@heroiclands/package-build 20.3.1 → 20.5.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 (195) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/CONTENT.md +322 -110
  3. package/README.md +21 -10
  4. package/bin/content-build.mjs +204 -92
  5. package/bin/package-build.mjs +48 -15
  6. package/config.mjs +6 -8
  7. package/content-config.mjs +518 -151
  8. package/docs/api.md +1352 -0
  9. package/docs/commands.md +1609 -0
  10. package/docs/configuration.md +1432 -0
  11. package/docs/content-format.md +73 -74
  12. package/docs/diagnostics.md +356 -0
  13. package/docs/getting-started.md +813 -0
  14. package/docs/project-setup.md +469 -0
  15. package/e2e.mjs +4 -4
  16. package/engine/actor-compiler.mjs +64 -61
  17. package/engine/address-charset.mjs +6 -6
  18. package/engine/address-diff.mjs +57 -52
  19. package/engine/anchored-sections.mjs +1 -1
  20. package/engine/anchors.mjs +1 -1
  21. package/engine/base-compiler.mjs +42 -38
  22. package/engine/bundle-notes.mjs +4 -4
  23. package/engine/bundles.mjs +17 -8
  24. package/engine/code-fences.mjs +3 -3
  25. package/engine/compendiums.mjs +8 -8
  26. package/engine/compile-corpus.mjs +4 -5
  27. package/engine/content-address.mjs +24 -24
  28. package/engine/content-charset.mjs +1 -1
  29. package/engine/content-format-check.mjs +24 -25
  30. package/engine/content-format.mjs +8 -8
  31. package/engine/content-html.mjs +202 -0
  32. package/engine/content-icons.mjs +196 -165
  33. package/engine/content-index.mjs +61 -40
  34. package/engine/content-links.mjs +74 -41
  35. package/engine/content-lint.mjs +64 -34
  36. package/engine/content-package.mjs +7 -9
  37. package/engine/content-slug.mjs +2 -2
  38. package/engine/content-tables.mjs +38 -34
  39. package/engine/content-tree.mjs +1 -1
  40. package/engine/derived-fields.mjs +174 -0
  41. package/engine/diagnostics.mjs +3 -4
  42. package/engine/document-subtypes.mjs +6 -7
  43. package/engine/field-reference.mjs +8 -8
  44. package/engine/field-spec.mjs +29 -30
  45. package/engine/folder-notes.mjs +18 -16
  46. package/engine/foreign-catalog.mjs +8 -9
  47. package/engine/foundry-entries.mjs +19 -20
  48. package/engine/frontmatter-lint.mjs +95 -103
  49. package/engine/frontmatter.mjs +11 -11
  50. package/engine/generate.mjs +44 -39
  51. package/engine/helpers.mjs +147 -84
  52. package/engine/homepage.mjs +20 -22
  53. package/engine/ids.mjs +15 -15
  54. package/engine/index-records.mjs +4 -4
  55. package/engine/index.mjs +26 -10
  56. package/engine/item-compiler.mjs +54 -29
  57. package/engine/item-docs.mjs +11 -13
  58. package/engine/item-registry.mjs +13 -13
  59. package/engine/journals.mjs +24 -15
  60. package/engine/macros.mjs +3 -3
  61. package/engine/map-notes.mjs +6 -6
  62. package/engine/metadata-index.mjs +5 -5
  63. package/engine/note-claims.mjs +51 -41
  64. package/engine/note-ids.mjs +5 -5
  65. package/engine/note-package.mjs +4 -4
  66. package/engine/note-renames.mjs +3 -3
  67. package/engine/note-schemas.mjs +3 -8
  68. package/engine/note-vocabulary.mjs +56 -54
  69. package/engine/pack-config.mjs +39 -25
  70. package/engine/pack-router.mjs +0 -0
  71. package/engine/pdf-build.mjs +464 -0
  72. package/engine/pdf-fonts.mjs +420 -0
  73. package/engine/pdf-render.mjs +798 -0
  74. package/engine/pdf-toc.mjs +525 -0
  75. package/engine/prose-config.mjs +9 -9
  76. package/engine/prose-lint.mjs +12 -13
  77. package/engine/region-events.mjs +2 -2
  78. package/engine/retired-fields.mjs +24 -22
  79. package/engine/runtime-only-fields.mjs +1 -1
  80. package/engine/scene-levels.mjs +1 -1
  81. package/engine/scenes.mjs +33 -25
  82. package/engine/schema-check.mjs +21 -21
  83. package/engine/schema-extract.mjs +1 -1
  84. package/engine/site-build.mjs +66 -50
  85. package/engine/site-index.mjs +17 -18
  86. package/engine/sql-tables.mjs +21 -13
  87. package/engine/subtype-registry.mjs +5 -4
  88. package/engine/system-block.mjs +18 -19
  89. package/engine/systems.mjs +2 -2
  90. package/engine/web-wikilinks.mjs +41 -38
  91. package/engine/wikilink-syntax.mjs +16 -16
  92. package/engine/wikilinks.mjs +40 -42
  93. package/hm3/actors.mjs +30 -22
  94. package/hm3/document-subtypes.mjs +5 -5
  95. package/hm3/index.mjs +2 -2
  96. package/hm3/item-builders.mjs +1 -1
  97. package/hm3/item-fields.mjs +3 -3
  98. package/hm3/items.mjs +28 -8
  99. package/hm3/template-priority.mjs +2 -2
  100. package/lang.mjs +3 -3
  101. package/manifest.mjs +17 -20
  102. package/package.json +1 -2
  103. package/release.mjs +66 -6
  104. package/sohl/actors.mjs +36 -28
  105. package/sohl/affiliation-standings.mjs +2 -2
  106. package/sohl/being-info.mjs +5 -5
  107. package/sohl/default-item-art.mjs +5 -5
  108. package/sohl/document-subtypes.mjs +5 -5
  109. package/sohl/index.mjs +3 -3
  110. package/sohl/item-builders.mjs +7 -7
  111. package/sohl/item-fields.mjs +8 -9
  112. package/sohl/items.mjs +28 -6
  113. package/sohl/kb-passes.mjs +5 -5
  114. package/sohl/note-schemas.mjs +12 -12
  115. package/sohl/skill-base.mjs +1 -1
  116. package/types/content-config.d.mts +91 -39
  117. package/types/e2e.d.mts +3 -3
  118. package/types/engine/actor-compiler.d.mts +45 -40
  119. package/types/engine/address-charset.d.mts +6 -6
  120. package/types/engine/address-diff.d.mts +60 -6
  121. package/types/engine/base-compiler.d.mts +27 -21
  122. package/types/engine/bundle-notes.d.mts +3 -3
  123. package/types/engine/bundles.d.mts +10 -1
  124. package/types/engine/code-fences.d.mts +3 -3
  125. package/types/engine/compendiums.d.mts +3 -3
  126. package/types/engine/compile-corpus.d.mts +1 -1
  127. package/types/engine/content-address.d.mts +20 -20
  128. package/types/engine/content-format-check.d.mts +6 -6
  129. package/types/engine/content-format.d.mts +2 -2
  130. package/types/engine/content-html.d.mts +78 -0
  131. package/types/engine/content-icons.d.mts +139 -92
  132. package/types/engine/content-index.d.mts +59 -15
  133. package/types/engine/content-links.d.mts +8 -8
  134. package/types/engine/content-lint.d.mts +8 -6
  135. package/types/engine/content-package.d.mts +6 -8
  136. package/types/engine/content-tables.d.mts +49 -18
  137. package/types/engine/derived-fields.d.mts +101 -0
  138. package/types/engine/diagnostics.d.mts +2 -2
  139. package/types/engine/document-subtypes.d.mts +3 -3
  140. package/types/engine/field-spec.d.mts +37 -39
  141. package/types/engine/folder-notes.d.mts +4 -5
  142. package/types/engine/foreign-catalog.d.mts +3 -3
  143. package/types/engine/foundry-entries.d.mts +6 -7
  144. package/types/engine/frontmatter-lint.d.mts +10 -9
  145. package/types/engine/frontmatter.d.mts +7 -7
  146. package/types/engine/generate.d.mts +7 -7
  147. package/types/engine/helpers.d.mts +128 -56
  148. package/types/engine/homepage.d.mts +16 -18
  149. package/types/engine/ids.d.mts +13 -13
  150. package/types/engine/index-records.d.mts +3 -3
  151. package/types/engine/index.d.mts +6 -0
  152. package/types/engine/item-compiler.d.mts +21 -5
  153. package/types/engine/item-docs.d.mts +2 -2
  154. package/types/engine/item-registry.d.mts +6 -6
  155. package/types/engine/journals.d.mts +12 -3
  156. package/types/engine/map-notes.d.mts +2 -2
  157. package/types/engine/metadata-index.d.mts +4 -4
  158. package/types/engine/note-claims.d.mts +30 -22
  159. package/types/engine/note-ids.d.mts +4 -4
  160. package/types/engine/note-package.d.mts +1 -1
  161. package/types/engine/note-renames.d.mts +3 -3
  162. package/types/engine/note-vocabulary.d.mts +31 -203
  163. package/types/engine/pack-config.d.mts +7 -7
  164. package/types/engine/pack-router.d.mts +1 -1
  165. package/types/engine/pdf-build.d.mts +42 -0
  166. package/types/engine/pdf-fonts.d.mts +30 -0
  167. package/types/engine/pdf-render.d.mts +144 -0
  168. package/types/engine/pdf-toc.d.mts +114 -0
  169. package/types/engine/prose-config.d.mts +9 -9
  170. package/types/engine/prose-lint.d.mts +3 -4
  171. package/types/engine/region-events.d.mts +2 -2
  172. package/types/engine/retired-fields.d.mts +10 -9
  173. package/types/engine/scenes.d.mts +10 -1
  174. package/types/engine/schema-check.d.mts +13 -13
  175. package/types/engine/site-build.d.mts +52 -25
  176. package/types/engine/site-index.d.mts +3 -4
  177. package/types/engine/sql-tables.d.mts +11 -5
  178. package/types/engine/subtype-registry.d.mts +3 -3
  179. package/types/engine/system-block.d.mts +3 -3
  180. package/types/engine/web-wikilinks.d.mts +7 -7
  181. package/types/engine/wikilink-syntax.d.mts +17 -17
  182. package/types/engine/wikilinks.d.mts +14 -14
  183. package/types/hm3/actors.d.mts +9 -1
  184. package/types/hm3/document-subtypes.d.mts +1 -1
  185. package/types/hm3/items.d.mts +9 -2
  186. package/types/hm3/template-priority.d.mts +1 -1
  187. package/types/manifest.d.mts +8 -8
  188. package/types/release.d.mts +15 -4
  189. package/types/sohl/actors.d.mts +12 -4
  190. package/types/sohl/affiliation-standings.d.mts +2 -2
  191. package/types/sohl/being-info.d.mts +2 -2
  192. package/types/sohl/document-subtypes.d.mts +1 -1
  193. package/types/sohl/items.d.mts +9 -2
  194. package/types/sohl/note-schemas.d.mts +1 -1
  195. package/MIGRATING.md +0 -608
package/lang.mjs CHANGED
@@ -29,13 +29,13 @@
29
29
  * `expandObject` **throws**: it cannot create a `Note` property on the string
30
30
  * `"The Pall"`. Foundry catches that throw and discards the **entire** file,
31
31
  * so one colliding pair drops every translation in it and each string renders
32
- * as its raw key (#636). A key must be a leaf **or** a branch, never both.
32
+ * as its raw key. A key must be a leaf **or** a branch, never both.
33
33
  * - **A Handlebars placeholder.** Foundry interpolates with `format()` and
34
34
  * SINGLE braces, so a `{{…}}` value renders literally unless some call site
35
- * happens to hand it to a Handlebars pass (#1353).
35
+ * happens to hand it to a Handlebars pass.
36
36
  * - **Data baked into a key segment.** A segment carrying anything but
37
37
  * `[A-Za-z0-9_-]` is a path or a UUID in a key, and a dotted payload is how
38
- * the collision above gets in (#636, #1351).
38
+ * the collision above gets in.
39
39
  *
40
40
  * Every function here is pure — it takes source text and returns findings, and
41
41
  * touches no filesystem and emits nothing. The caller owns discovery and
package/manifest.mjs CHANGED
@@ -18,16 +18,13 @@
18
18
  * so there is one job here with two spellings: assemble the manifest from the
19
19
  * repository's configuration and write it into the build stage.
20
20
  *
21
- * **There is no template any more.** A manifest used to be a hand-authored
22
- * `system.template.json` that this module stamped a few fields into which
23
- * made it the one build input still written as JSON, by hand, per repository,
24
- * with no schema and nothing checking it. Worse, it declared facts the
25
- * configuration also declared: the pack list twice, in two formats, with
26
- * nothing checking that the pairs agreed. `sohl-kethira-basic` hand-maintained
27
- * its whole `module.json`, and its `download` named an older version than the
28
- * module claimed.
21
+ * **There is no template.** A `system.template.json` this module stamped a few
22
+ * fields into would be the one build input written as JSON, by hand, per
23
+ * repository, with no schema and nothing checking it and it would declare
24
+ * facts the configuration also declares, the pack list twice in two formats,
25
+ * with nothing checking that the pairs agree.
29
26
  *
30
- * So the manifest is generated (#9). Three kinds of key end up in it:
27
+ * So the manifest is generated. Three kinds of key end up in it:
31
28
  *
32
29
  * - **Declared** — the `packageBuild.manifest` block, emitted unchanged, so a
33
30
  * key Foundry adds in a later version needs no release of this package.
@@ -122,7 +119,7 @@ export function releaseUrls({ repoUrl, version, artifact }) {
122
119
  }
123
120
 
124
121
  /**
125
- * Where this release publishes its content index (#239).
122
+ * Where this release publishes its content index.
126
123
  *
127
124
  * **Pinned to this version, like `download` and unlike `manifest`.** A
128
125
  * consumer reaches this URL by reading the dependency's manifest, so the
@@ -183,7 +180,7 @@ const MANIFEST_KEY_ORDER = Object.freeze([
183
180
  /**
184
181
  * The manifest's `packs`, derived from the one pack list the build already has.
185
182
  *
186
- * The two used to be written separately `package-build.config.yaml` declared
183
+ * The two are not written separately, with `package-build.config.yaml` declaring
187
184
  * a pack's name and type, and the manifest template declared them again beside
188
185
  * a label, a path and a system id, with nothing checking that the pairs agreed.
189
186
  * They are one list now.
@@ -265,12 +262,12 @@ function namedPacks(folders, at) {
265
262
  * `styles`, `languages`), and a staged file is a different relation, checked
266
263
  * against the stage rather than against configuration. So this is the one place
267
264
  * a declaration can go stale against a value the build already computed — and
268
- * until now nothing compared them (#81).
265
+ * until now nothing compared them.
269
266
  *
270
267
  * `HarnMaster-3-FoundryVTT` shipped the consequence: its folder named four
271
268
  * packs, three of which had not existed since the compendium was consolidated,
272
269
  * and omitted `items` — 1,577 of 1,597 documents, loose in Foundry's compendium
273
- * browser, with the build reporting nothing (HM3#420).
270
+ * browser, with the build reporting nothing.
274
271
  *
275
272
  * **The two findings are not the same finding**, and giving them one severity
276
273
  * gets one of them wrong:
@@ -343,14 +340,14 @@ export function packFolderFindings({ packFolders, packs = [] }) {
343
340
  *
344
341
  * `relationships` is the one manifest block with a second reader.
345
342
  * `@heroiclands/content-build` consumes it too, and v1.8.0 added
346
- * `itemCatalog: true` as an opt-in on a declared dependency
347
- * (content-build#82): it selects that package's Item packs as a resolution
343
+ * `itemCatalog: true` as an opt-in on a declared dependency: it selects that
344
+ * package's Item packs as a resolution
348
345
  * source for the actors pass. That is an instruction to the build, not a fact
349
346
  * about the shipped package — Foundry's relationship schema does not define
350
347
  * it, and someone reading a published manifest cannot tell a build directive
351
348
  * from a declaration about what the package needs.
352
349
  *
353
- * So the block is filtered rather than copied whole (#29). The rule is the
350
+ * So the block is filtered rather than copied whole. The rule is the
354
351
  * distinction, not the name: a key listed here answers *how is this built?*,
355
352
  * and every key that survives answers *what does this package depend on?*.
356
353
  * `itemCatalog` is the first build-time key to land on a relationship and is
@@ -441,7 +438,7 @@ export function buildManifest({ config, packageJson, artifact, flags }) {
441
438
  };
442
439
  if (config.compatibility) derived.compatibility = config.compatibility;
443
440
 
444
- // `requiresSystem` is the gate half of the declare/require split (#48). It
441
+ // `requiresSystem` is the gate half of the declare/require split. It
445
442
  // emits the `relationships.systems` entry Foundry's `supportsSystem` reads,
446
443
  // reusing the `systems:` declaration rather than restating it — a second
447
444
  // transcription is free to disagree with what it copied, which is how
@@ -476,8 +473,8 @@ export function buildManifest({ config, packageJson, artifact, flags }) {
476
473
 
477
474
  const merged = { ...declared, ...derived };
478
475
 
479
- // The index every consumer resolves this package's addresses through
480
- // (#239). Written unconditionally, because a package that publishes no
476
+ // The index every consumer resolves this package's addresses through.
477
+ // Written unconditionally, because a package that publishes no
481
478
  // index is one nothing can link into — and the failure of an absent one is
482
479
  // a dead link in somebody else's build, which is exactly the kind of
483
480
  // silence this replaced the vendored manifest to end.
@@ -562,7 +559,7 @@ function reportPackFolders(findings, configFile) {
562
559
  * The declared `packFolders` is checked against the derived `packs[]` first,
563
560
  * and an unresolvable name **stops the write**: a manifest already known to
564
561
  * describe packs the package does not ship should not reach the stage, where
565
- * the next command would deploy it (#81). See {@link packFolderFindings} for
562
+ * the next command would deploy it. See {@link packFolderFindings} for
566
563
  * the rule and why its two findings carry different severities.
567
564
  *
568
565
  * @param {object} options - As {@link buildManifest}, plus where to write.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "20.3.1",
3
+ "version": "20.5.0",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -101,7 +101,6 @@
101
101
  "CHANGELOG-content-build.md",
102
102
  "CHANGELOG.md",
103
103
  "CONTENT.md",
104
- "MIGRATING.md",
105
104
  "README.md",
106
105
  "bin",
107
106
  "bundle.mjs",
package/release.mjs CHANGED
@@ -19,7 +19,7 @@
19
19
  * advertises, so a release publishes `<artifact>.zip`, the whole staged tree,
20
20
  * and `<artifact>.json` beside it, which is what an already installed package
21
21
  * re-fetches to notice a new version. A package that ships content publishes a
22
- * third: the content index other packages resolve its addresses through (#239),
22
+ * third: the content index other packages resolve its addresses through,
23
23
  * named by the `flags.metadataUrl` the manifest advertises. Every name is fixed
24
24
  * by what the manifest says, not chosen here — see `manifest.mjs`.
25
25
  *
@@ -37,7 +37,7 @@ import path from "node:path";
37
37
  // archiver 8 is pure ESM and exports **classes**, with no default export. The
38
38
  // old `import archiver from "archiver"` factory call throws at import —
39
39
  // `does not provide an export named 'default'` — which is how this repository's
40
- // release job came to fail before a single byte was written (#1683).
40
+ // release job came to fail before a single byte was written.
41
41
  import { ZipArchive } from "archiver";
42
42
 
43
43
  /**
@@ -57,10 +57,17 @@ import { ZipArchive } from "archiver";
57
57
  * Determines both asset names.
58
58
  * @param {string} [opts.metadataDir] - Where the build writes its content
59
59
  * index, consulted when the advertised file was not staged.
60
+ * @param {boolean} [opts.pdf] - Whether to build the book that ships beside the
61
+ * archive. `true` by default; `false` skips the build and reports the skip.
60
62
  * @returns {Promise<{zip: string, manifest: string, metadata?: string,
61
- * bytes: number, version: string}>} The paths written, the archive's size,
62
- * and the version the manifest declares. `metadata` is absent when the
63
- * manifest advertises no content index.
63
+ * pdf?: string, pdfFindings: object[], pdfSkipped: string|null,
64
+ * bytes: number, version: string}>} The paths written, what the book build
65
+ * found, the archive's size, and the version the manifest declares.
66
+ * `metadata` is absent when the manifest advertises no content index, and
67
+ * `pdf` is absent when no book was written. `pdfSkipped` is `null` when a
68
+ * book was built, and otherwise the reason none was — itself `null` when the
69
+ * book builder could not be loaded, which is reported through
70
+ * `pdfFindings`.
64
71
  * @throws {Error} When the stage has no manifest — there is nothing to release,
65
72
  * and an archive without one installs as nothing.
66
73
  */
@@ -69,6 +76,7 @@ export async function packRelease({
69
76
  outDir = "build/dist",
70
77
  artifact = "system",
71
78
  metadataDir = "build/content-index",
79
+ pdf = true,
72
80
  } = {}) {
73
81
  const stage = path.resolve(stageDir);
74
82
  const out = path.resolve(outDir);
@@ -112,17 +120,69 @@ export async function packRelease({
112
120
 
113
121
  const metadata = await publishMetadataIndex({ manifest, stage, out, metadataDir });
114
122
 
123
+ // Last, and never fatal: the archive and the manifest are the release, and
124
+ // a book that failed to set is a reported problem rather than a reason to
125
+ // publish neither.
126
+ const book =
127
+ pdf ?
128
+ await packReleasePdf({ out, version: manifest.version })
129
+ : { pdf: null, findings: [], reason: "the release was asked not to build one" };
130
+
115
131
  return {
116
132
  zip: zipPath,
117
133
  manifest: path.join(out, manifestName),
118
134
  ...(metadata ? { metadata } : {}),
135
+ ...(book.pdf ? { pdf: book.pdf } : {}),
136
+ pdfFindings: book.findings,
137
+ pdfSkipped: book.pdf ? null : book.reason,
119
138
  bytes: archive.pointer(),
120
139
  version: manifest.version,
121
140
  };
122
141
  }
123
142
 
124
143
  /**
125
- * Place the content index the manifest advertises beside the archive (#239).
144
+ * Build the book that ships beside the archive.
145
+ *
146
+ * **Imported when it is used, not when this module is.** The book build pulls
147
+ * in a markdown parser, DuckDB and the whole content engine; `release.mjs`
148
+ * otherwise exists to zip a directory, and every consumer that publishes no
149
+ * book would pay for that graph on `import`. A dynamic import inside the one
150
+ * function that needs it keeps the cost where the benefit is.
151
+ *
152
+ * **Not building is the normal case and never an error.** A package publishing
153
+ * only a homepage, one with no `pdf:` block and one with no content tree have
154
+ * each said they publish no book. Four of the six packages that install this
155
+ * toolchain are in exactly that position, so a release that failed for the
156
+ * absence of a PDF would break more releases than it helped.
157
+ *
158
+ * @param {object} opts - Options.
159
+ * @param {string} opts.out - The release directory.
160
+ * @param {string} opts.version - The version the manifest declares.
161
+ * @returns {Promise<{pdf: string|null, findings: object[], reason: string|null}>}
162
+ * What was built, and what was found on the way.
163
+ */
164
+ async function packReleasePdf({ out, version }) {
165
+ let buildPdf;
166
+ try {
167
+ ({ buildPdf } = await import("./engine/pdf-build.mjs"));
168
+ } catch (err) {
169
+ return {
170
+ pdf: null,
171
+ findings: [
172
+ {
173
+ severity: "warning",
174
+ message: `the book builder could not be loaded: ${err.message}`,
175
+ },
176
+ ],
177
+ reason: null,
178
+ };
179
+ }
180
+ const result = await buildPdf({ out, version });
181
+ return { pdf: result.pdf, findings: result.findings, reason: result.reason };
182
+ }
183
+
184
+ /**
185
+ * Place the content index the manifest advertises beside the archive.
126
186
  *
127
187
  * **The asset's name comes from the manifest, not from here.** `flags.metadataUrl`
128
188
  * is the URL every consumer fetches, so its basename is by definition the name
package/sohl/actors.mjs CHANGED
@@ -17,21 +17,21 @@
17
17
  * The machinery an actor of any system needs — the predefined-item catalogue,
18
18
  * reference translation through the system's map, embedding and its stable ids,
19
19
  * the anchored prose sections — lives in
20
- * {@link module:engine/actor-compiler}, where a second system reaches it
21
- * (#139). What is left here is SoHL's data model: the body structure and its
20
+ * {@link module:engine/actor-compiler}, where a second system reaches it.
21
+ * What is left here is SoHL's data model: the body structure and its
22
22
  * movement profiles, the attributes-and-items frontmatter that becomes embedded
23
23
  * documents, the opening mastery level a skill is baked with, and the `system`
24
24
  * block itself.
25
25
  *
26
26
  * One content type today, and the actor subtype it produces is **declared**
27
27
  * rather than assumed to be the same word: `sohl/document-subtypes.mjs` maps
28
- * `being` → `Actor` / `being`, and this pass looks it up (#79). It read
28
+ * `being` → `Actor` / `being`, and this pass looks it up. It read
29
29
  * `ACTOR_VAULT_TYPE = "being"` here and emitted `type: "being"` several hundred
30
30
  * lines below, which made the two vocabularies agree by coincidence.
31
31
  *
32
32
  * It was two content types — `character` and `creature` — which compiled to the
33
33
  * same `being` with no branch anywhere between them; they were retired in
34
- * SoHL#1580 and are now reported by `assertTypeNotRetired` in
34
+ * retired, and are reported by `assertTypeNotRetired` in
35
35
  * `engine/ids.mjs`.
36
36
  *
37
37
  * Attributes (`sohl.attributes` map) become embedded attribute items with
@@ -56,16 +56,16 @@ import {
56
56
  import { openingMasteryLevel } from "./skill-base.mjs";
57
57
  import { SystemActorCompiler, renderSection } from "../engine/actor-compiler.mjs";
58
58
  // Which Foundry Actor subtype a note's `type` compiles into. Looked up in the
59
- // system's declared map, never inferred from the type itself (#79).
59
+ // system's declared map, never inferred from the type itself.
60
60
  import { documentSubtype } from "../engine/document-subtypes.mjs";
61
61
  import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
62
62
  // The note-level `sohl:` block: `sohl.system` onto the document's `system`
63
63
  // verbatim, and `sohl.img` / `sohl.effects` / `sohl.flags` overriding their
64
- // shared top-level forms for this system alone (#58).
64
+ // shared top-level forms for this system alone.
65
65
  import { blockProperty, mergeSystemData } from "../engine/system-block.mjs";
66
66
  import { readField, retiredTopLevelKey } from "../engine/field-spec.mjs";
67
67
  // The retirement window's reports, shared with the frontmatter lint so the two
68
- // cannot say different things about the same key (#305, #332).
68
+ // cannot say different things about the same key.
69
69
  import {
70
70
  legacyKeyMessage,
71
71
  locateFrontmatterKey,
@@ -76,7 +76,7 @@ import {
76
76
  * The system this pass compiles for — the block its notes write.
77
77
  *
78
78
  * Read from the map rather than spelled here, so the block name and the subtype
79
- * map are one statement (#58/#79).
79
+ * map are one statement.
80
80
  *
81
81
  * @type {string}
82
82
  */
@@ -98,7 +98,7 @@ const DEFAULT_IMG = {
98
98
  * and the note's top level and nothing else — so `data.portrait`, the position
99
99
  * the specification names and `sohl-thalorna` writes on 646 beings, was
100
100
  * invisible, and `?? defaultImg` on the next line turned every miss into the
101
- * generic person icon rather than into a complaint (#332). Going through
101
+ * generic person icon rather than into a complaint. Going through
102
102
  * {@link module:engine/field-spec.readField} is what makes the mapping table
103
103
  * executable here as it already is for HM3's `data.species`.
104
104
  *
@@ -115,7 +115,7 @@ const PORTRAIT_FIELD = Object.freeze({
115
115
  shape: "path",
116
116
  // The two empties survive, because the caller's `?? defaultImg` is what
117
117
  // tells them apart: `null` and an absent key mean "no art named, default
118
- // me", `""` means "ship blank on purpose" (#218).
118
+ // me", `""` means "ship blank on purpose".
119
119
  read: (raw) => resolveImg(raw),
120
120
  default: null,
121
121
  describe: "Path to the portrait image.",
@@ -190,11 +190,19 @@ function extractBodyAndMovement(fm) {
190
190
  };
191
191
  }
192
192
 
193
+ /**
194
+ * SoHL's Actor compile pass.
195
+ *
196
+ * Declares SoHL's note-type → document-subtype map and builds a `being` note's
197
+ * document: the body structure and its movement profiles, the embedded
198
+ * attribute and item documents the frontmatter names, and the `system` block.
199
+ * Everything else is {@link module:engine/actor-compiler}'s.
200
+ */
193
201
  export class Actors extends SystemActorCompiler {
194
202
  /**
195
203
  * SoHL's note-type → document-subtype map — the one declaration that says
196
204
  * which block this pass reads, which notes it claims, and what each
197
- * becomes (#79). It is also what every embedded reference is translated
205
+ * becomes. It is also what every embedded reference is translated
198
206
  * through, which is why a subclass replaces one thing and not two.
199
207
  *
200
208
  * @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
@@ -298,7 +306,7 @@ export class Actors extends SystemActorCompiler {
298
306
  }
299
307
 
300
308
  /**
301
- * Bake each unopened skill's opening mastery level into the document (#46).
309
+ * Bake each unopened skill's opening mastery level into the document.
302
310
  *
303
311
  * A skill whose `masteryLevelBase` is still null once the note's frontmatter
304
312
  * has been merged onto the catalogue entry is *not yet opened*, and the
@@ -352,7 +360,7 @@ export class Actors extends SystemActorCompiler {
352
360
  const id = fm.id;
353
361
  const ctx = `actor "${name}"`;
354
362
  // The document's own subtype, and the art that goes with it. Both are
355
- // looked up from the note's `type` rather than spelled here (#79).
363
+ // looked up from the note's `type` rather than spelled here.
356
364
  const subType = /** @type {string} */ (
357
365
  documentSubtype(this.constructor.documentSubtypes, fm.type, fm, {
358
366
  absPath: this.currentNote?.absPath,
@@ -365,13 +373,13 @@ export class Actors extends SystemActorCompiler {
365
373
  const { value: authoredFolder, isAddress } = folderField(fm);
366
374
  const folder = this.folderResolver(authoredFolder, { isAddress });
367
375
 
368
- // The two retiring positions a declared field may be read from (#305,
369
- // #332). **Warnings**, on the pattern every retirement in this package
376
+ // The two retiring positions a declared field may be read from.
377
+ // **Warnings**, on the pattern every retirement in this package
370
378
  // follows: the note compiles to the correct document either way, so
371
379
  // reddening a tree over one would refuse before the sweep rather than
372
- // after it. What they buy is a count — the whole reason #332 was
373
- // invisible for so long is that nothing said which position a value
374
- // had come from, and a default is indistinguishable from a miss.
380
+ // after it. What they buy is a count — without one, nothing says which
381
+ // position a value came from, and a default is indistinguishable from
382
+ // a miss.
375
383
  const portraitReports = {
376
384
  block: SYSTEM,
377
385
  onLegacyKey: (field) =>
@@ -398,25 +406,25 @@ export class Actors extends SystemActorCompiler {
398
406
  const system = {
399
407
  // The frontmatter shortcode is the actor's stable `(type, shortcode)`
400
408
  // key — and, for a being that is an archetype, its archetype
401
- // identity (the dedup/override key of the Create-dialog picker, #604).
409
+ // identity (the dedup/override key of the Create-dialog picker).
402
410
  shortcode: fm.shortcode || "",
403
411
  // Required nullable number: a priority, or `null` for a being that
404
- // is not a template (#126 / archetype contract #604). The field was
405
- // `system.archetype` until #266 / sohl#1836; the emitted key moves
412
+ // is not a template (the archetype contract). `system.archetype`
413
+ // is the legacy spelling; the emitted key moves
406
414
  // with the schema, since an undeclared `system` key is discarded at
407
415
  // construction without a warning.
408
416
  templatePriority: systemTemplatePriority(fm, ctx),
409
- // Nullish, not `||` (#218): a note that names no portrait gets the
417
+ // Nullish, not `||`: a note that names no portrait gets the
410
418
  // subtype's default, one that writes `""` ships blank on purpose.
411
419
  // Resolved through the declaration so `data.portrait` is reached at
412
- // all — see {@link PORTRAIT_FIELD} (#332).
420
+ // all — see {@link PORTRAIT_FIELD}.
413
421
  portrait: readField(PORTRAIT_FIELD, fm, portraitReports) ?? defaultImg,
414
422
  appearance: renderSection(body || "", "appearance"),
415
423
  dossier: renderSection(body || "", "dossier"),
416
424
  };
417
425
 
418
426
  // Fill `system.body` (+ the base-actor movement fields) from the being's
419
- // frontmatter, rather than embedding a corpus item (#535). The `sohl`
427
+ // frontmatter, rather than embedding a corpus item. The `sohl`
420
428
  // block mirrors `system` field-for-field: `sohl.body` nests the body
421
429
  // (`structure` / `weight` / …), with `currentMoveMedium` /
422
430
  // `movementProfiles` flat alongside it. An **incorporeal** being omits
@@ -441,14 +449,14 @@ export class Actors extends SystemActorCompiler {
441
449
  }
442
450
 
443
451
  // Whatever the note authors under `sohl.system`, at the DataModel's own
444
- // paths (#58). This pass has no field declaration, so it claims
452
+ // paths. This pass has no field declaration, so it claims
445
453
  // nothing: every authored path is the author's, and the fields above
446
454
  // are what a note that authors none still gets.
447
455
  mergeSystemData(system, fm, { block: SYSTEM });
448
456
  this.reportUndeclaredSystemData(fm, SYSTEM, "Actor", subType);
449
457
  // And what this pass wrote itself. There is no field declaration for a
450
458
  // being at all, so *every* key here is a compiler emission — including
451
- // `templatePriority` (#126), which nothing compared until #155.
459
+ // `templatePriority`, which nothing compared until.
452
460
  this.reportEmittedSystemData(system, {
453
461
  fm,
454
462
  block: SYSTEM,
@@ -462,7 +470,7 @@ export class Actors extends SystemActorCompiler {
462
470
  return {
463
471
  name,
464
472
  type: subType,
465
- // Nullish, not `||` — see the portrait above (#218).
473
+ // Nullish, not `||` — see the portrait above.
466
474
  img: resolveImg(blockProperty(fm, SYSTEM, "img")) ?? defaultImg,
467
475
  _id: id,
468
476
  system,
@@ -483,7 +491,7 @@ export class Actors extends SystemActorCompiler {
483
491
  ownership: { default: 0 },
484
492
  // Whatever the note authors, and nothing else. The priority used to
485
493
  // be spliced in here as `flags.sohl.docArchetype`; it is a schema
486
- // field now and sits in `system` (#126).
494
+ // field now and sits in `system`.
487
495
  flags: blockProperty(fm, SYSTEM, "flags", {}),
488
496
  _stats: this.stats,
489
497
  _key: `!actors!${id}`,
@@ -13,12 +13,12 @@
13
13
 
14
14
  /**
15
15
  * The affiliation standings an authored `relation` map may use — one
16
- * affiliation's stance toward another (#1404).
16
+ * affiliation's stance toward another.
17
17
  *
18
18
  * Plain ESM for the same reason `./default-item-art.mjs` is: the pack scripts
19
19
  * run under bare `node`, so they cannot read the runtime's TypeScript
20
20
  * `AFFILIATION_STANDING`. This module is where the pipeline reads the list
21
- * from, rather than restating it inside a compiler (#1510).
21
+ * from, rather than restating it inside a compiler.
22
22
  *
23
23
  * The runtime keeps its own `defineType("SOHL.Affiliation.Standing", { … })`
24
24
  * declaration in `src/utils/constants.ts`, because that literal is *parsed out
@@ -24,9 +24,9 @@
24
24
  * **It lives here because it was living in two places.** Both
25
25
  * `Song-of-Heroic-Lands-FoundryVTT` and `sohl-thalorna` carried a copy, and the
26
26
  * copies drifted: SoHL's caller still gated the derivation on `character` and
27
- * `creature`, the two types #1580 merged into `being`, so it had matched
27
+ * `creature`, the two legacy spellings of `being`, so it had matched
28
28
  * nothing since the merge and all 95 of its being pages published with empty
29
- * sidebar sections (SoHL#1696). thalorna's copy checked `being` and was right.
29
+ * sidebar sections. thalorna's copy checked `being` and was right.
30
30
  * Nothing failed in either repository; the pages built and shipped.
31
31
  *
32
32
  * {@link isBeing} exists for that reason. The bug was not in the derivation —
@@ -36,14 +36,14 @@
36
36
  * @module
37
37
  */
38
38
 
39
- // The retirement window for a renamed note type (#78): an embedded reference
39
+ // The retirement window for a renamed note type: an embedded reference
40
40
  // still spelling `armorgear` names the same gear group as `armor`.
41
41
  import { currentType } from "../engine/ids.mjs";
42
42
 
43
43
  /**
44
44
  * The note `type` whose pages carry a being info block.
45
45
  *
46
- * One name, since #1580 merged `character` and `creature` into the `being` they
46
+ * One name: `character` and `creature` are the `being` they
47
47
  * had always compiled into. The retired names are deliberately **not** accepted
48
48
  * as aliases: they throw elsewhere in the system, and tolerating them here
49
49
  * would hide the next drift of this kind rather than surface it.
@@ -68,7 +68,7 @@ export function isBeing(fm) {
68
68
  * added in a single place rather than in each consumer's site build.
69
69
  *
70
70
  * Keyed by **note** type, which is what a being's embedded `(type, shortcode)`
71
- * references spell — and since #78 three of those are no longer the document
71
+ * references spell — and three of those are not the document
72
72
  * subtype they compile into. A reference still on a renamed spelling is
73
73
  * normalised at the lookup below rather than given a second row here.
74
74
  *
@@ -27,15 +27,15 @@
27
27
  * runtime can both import it. A `.ts` module could not be required by the build
28
28
  * scripts (they run under bare `node`, without the bundler that resolves the
29
29
  * `@src` alias and strips types). Keeping one map here is what prevents the
30
- * build-time and runtime defaults from drifting apart (issue #932 was exactly
30
+ * build-time and runtime defaults from drifting apart (which is exactly
31
31
  * that drift: the builder had a default, runtime did not).
32
32
  *
33
33
  * It lives in this package rather than in the system's `src/` tree because the
34
- * pack pipeline is installed as a dependency (#1501): a relative path out of
34
+ * pack pipeline is installed as a dependency: a relative path out of
35
35
  * the package would resolve to garbage once the pipeline runs from
36
36
  * `node_modules`. The runtime imports it back through the package's
37
- * `./sohl/default-item-art` entry point, so there is still exactly one map
38
- * (#1510). Injecting the map through configuration instead would re-open #932,
37
+ * `./sohl/default-item-art` entry point, so there is still exactly one map.
38
+ * Injecting the map through configuration instead would re-open that drift,
39
39
  * because the two sides would once more be free to disagree.
40
40
  *
41
41
  * Paths are fully resolved (`systems/sohl/...`) — the served path both layers
@@ -44,7 +44,7 @@
44
44
  * **The keys are SoHL *document* subtypes, not markdown note types.** The
45
45
  * runtime reads this with `itemData.type`, which is a Foundry Item subtype, and
46
46
  * that is the vocabulary a compendium document carries. The two vocabularies
47
- * were the same string until #78 renamed three note types (`armorgear` →
47
+ * would be the same string but for three renamed note types (`armorgear` →
48
48
  * `armor`, and its two siblings), and this map stayed on the document side
49
49
  * because that is the side the runtime cannot translate: `sohl/item-builders.mjs`
50
50
  * asks SoHL's own note-type → subtype map before it looks art up here, so the
@@ -13,11 +13,11 @@
13
13
 
14
14
  /**
15
15
  * **SoHL's note-type → document-subtype map** — which Foundry document, and
16
- * which subtype of it, a note of each content type compiles into (#79).
16
+ * which subtype of it, a note of each content type compiles into.
17
17
  *
18
18
  * The mechanism is `engine/document-subtypes.mjs`; what a *game system* does
19
19
  * with it is here, which is the `engine/` ÷ `sohl/` line this package draws
20
- * everywhere else (#36). A second system declares its own map in its own half
20
+ * everywhere else. A second system declares its own map in its own half
21
21
  * and shares nothing but the mechanism.
22
22
  *
23
23
  * **Every row is written out, identity rows included.** `skill → skill` looks
@@ -31,7 +31,7 @@
31
31
  * **Three rows are no longer the identity, and that is what the map was for.**
32
32
  * `armor`, `concoction` and `projectile` were renamed from the `…gear`
33
33
  * spellings that named SoHL's *document* subtype rather than the thing the note
34
- * is about (#78); the subtypes themselves did not move, so those three rows now
34
+ * is about; the subtypes themselves did not move, so those three rows now
35
35
  * say two different words where they used to say one twice. Landing the rename
36
36
  * cost exactly what having the map first promised it would: a data change on
37
37
  * three lines here, and no mechanism change anywhere.
@@ -40,7 +40,7 @@
40
40
  * before this map is consulted — so a tree that has not swept its 30,741
41
41
  * embedded `(type, shortcode)` references compiles byte-identically. `weapon`
42
42
  * is deliberately **not** among them: both systems call that document a
43
- * `weapongear`, so the name says nothing SoHL-specific and #78 left it alone.
43
+ * `weapongear`, so the name says nothing SoHL-specific and is left alone.
44
44
  *
45
45
  * **What this map is not.** It says which document a note becomes, never what
46
46
  * that document contains: the `system` block comes from the item registry's
@@ -59,7 +59,7 @@ import { defineDocumentSubtypes } from "../engine/document-subtypes.mjs";
59
59
  *
60
60
  * The Item rows are the thirteen types the item registry declares; the one
61
61
  * Actor row is `being`, which was two types (`character` and `creature`)
62
- * compiling to the same actor until they were retired in SoHL#1580. Types this
62
+ * compiling to the same actor before they were retired. Types this
63
63
  * map does not name — `doc`, `macro`, the three map types — compile into
64
64
  * documents that carry no system subtype at all, so they have no row and never
65
65
  * needed one.
package/sohl/index.mjs CHANGED
@@ -16,7 +16,7 @@
16
16
  * Lands data model that a generic content module must never receive.
17
17
  *
18
18
  * The item-type registry and its builders, the items and actors compilers, the
19
- * default-art map, and the affiliation standings live here (#1512). Nothing in
19
+ * default-art map, and the affiliation standings live here. Nothing in
20
20
  * `@heroiclands/package-build/engine` exports any of it, so an adventure module
21
21
  * that builds journals, macros, and scenes never receives `buildWeaponGear`.
22
22
  *
@@ -33,7 +33,7 @@
33
33
  /** The item-type registry: every type that compiles into an Item, and its builder. */
34
34
  export * as itemBuilders from "./item-builders.mjs";
35
35
 
36
- /** This system's note-type → document-subtype map, identity rows included (#79). */
36
+ /** This system's note-type → document-subtype map, identity rows included. */
37
37
  export * as documentSubtypes from "./document-subtypes.mjs";
38
38
 
39
39
  /** The Item compiler. */
@@ -47,7 +47,7 @@ export * as kbPasses from "./kb-passes.mjs";
47
47
 
48
48
  // Flat as well as namespaced: the Foundry runtime imports these by name through
49
49
  // their own entry points, and they were this barrel's surface before the
50
- // compilers arrived (#1510).
50
+ // compilers arrived.
51
51
  export { DEFAULT_ITEM_ART, defaultItemArt } from "./default-item-art.mjs";
52
52
  export { AFFILIATION_STANDINGS } from "./affiliation-standings.mjs";
53
53
  export { BEING_TYPE, GEAR_TYPE_TO_KEY, deriveBeingInfo, isBeing } from "./being-info.mjs";
@@ -18,20 +18,20 @@
18
18
  * There is one list, not two. This repository hands {@link ITEM_BUILDERS} to
19
19
  * the build as `itemBuilders` in `package-build.config.yaml`, and `itemTypes()`
20
20
  * — the whitelist — is derived from that table's own keys, so a type cannot be
21
- * whitelisted for compilation without a builder to compile it. Previously the
21
+ * whitelisted for compilation without a builder to compile it. Split, the
22
22
  * whitelist and the builder table were maintained by hand and had already
23
- * drifted: `trait` — an item type **retired in #651**, absent from
23
+ * drifted: `trait` — a **retired** item type, absent from
24
24
  * `documentTypes.Item` and reported by world migration as unrecognized — was
25
25
  * still advertised as compilable, so a `type: trait` note passed the gate and
26
26
  * then died on `BUILDERS[type] is not a function`, swallowed as a per-file
27
- * error (#1504).
27
+ * error.
28
28
  *
29
29
  * **The builders are generated, not written.** Each type's `system` builder
30
30
  * comes from its field declaration in `item-fields.mjs` by way of
31
31
  * {@link buildFromFields}, so the vocabulary a note may write is readable data
32
32
  * rather than statements buried in a function body — which is what lets the
33
- * authoring reference be generated and a note be linted against its type
34
- * (#22). One consequence worth naming: the registry's keys are now
33
+ * authoring reference be generated and a note be linted against its type.
34
+ * One consequence worth naming: the registry's keys are now
35
35
  * {@link ITEM_FIELDS}'s keys, so adding an item type is one edit *there* (plus
36
36
  * its `documentTypes.Item` declaration and its default art), and removing one
37
37
  * is likewise a single deletion.
@@ -43,7 +43,7 @@
43
43
  * own evaluation. The table travels into configuration; the engine's
44
44
  * `item-registry.mjs` reads it back out and the Item compiler dispatches
45
45
  * through that, which is how a consumer's own table is the one its notes
46
- * compile with (#1563).
46
+ * compile with.
47
47
  */
48
48
 
49
49
  import { defaultItemArt } from "./default-item-art.mjs";
@@ -86,7 +86,7 @@ const FINALIZERS = Object.freeze({
86
86
  *
87
87
  * **The art map is keyed by the document subtype**, which the runtime reads
88
88
  * with a Foundry `Item`'s own `type`, so this asks SoHL's map what an `armor`
89
- * note becomes before looking art up (#78). Translating on the build side is
89
+ * note becomes before looking art up. Translating on the build side is
90
90
  * the only place it can happen: the runtime has no note in hand. Every SoHL
91
91
  * Item row is one-to-one, so `documentSubtype` needs no frontmatter and cannot
92
92
  * throw for a discriminator.