@heroiclands/package-build 23.1.1 → 23.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/bin/content-commands.mjs +68 -149
  3. package/bin/package-build.mjs +56 -10
  4. package/ci/check-banners.mjs +42 -6
  5. package/ci/confirm-npm-version.mjs +135 -0
  6. package/ci/content-format-reference.mjs +14 -5
  7. package/config.mjs +1 -0
  8. package/container.mjs +185 -4
  9. package/content-config.mjs +333 -210
  10. package/deploy.mjs +116 -10
  11. package/docs/api.md +238 -246
  12. package/docs/authoring/assets.md +11 -5
  13. package/docs/authoring/frontmatter.md +1 -1
  14. package/docs/authoring/links-and-markup.md +87 -0
  15. package/docs/commands.md +138 -99
  16. package/docs/configuration.md +277 -125
  17. package/docs/content-format.md +2 -0
  18. package/docs/diagnostics.md +48 -13
  19. package/docs/guides/book.md +341 -21
  20. package/docs/guides/e2e.md +13 -2
  21. package/docs/guides/icons.md +14 -4
  22. package/docs/guides/module-project.md +18 -8
  23. package/docs/guides/release-and-deploy.md +32 -0
  24. package/docs/guides/site.md +5 -2
  25. package/docs/project-setup.md +30 -10
  26. package/docs/reference/format-details.md +373 -170
  27. package/docs/reference/note-types.md +128 -109
  28. package/docs/reference/theme-pages.md +31 -6
  29. package/e2e.mjs +140 -28
  30. package/engine/actor-compiler.mjs +22 -31
  31. package/engine/address-charset.mjs +4 -17
  32. package/engine/address-fields.mjs +1 -1
  33. package/engine/address-render.mjs +12 -1
  34. package/engine/address-values.mjs +6 -0
  35. package/engine/address.mjs +46 -33
  36. package/engine/art-fields.mjs +30 -5
  37. package/engine/art-slots.mjs +10 -1
  38. package/engine/asset-bindings.mjs +12 -1
  39. package/engine/base-compiler.mjs +34 -65
  40. package/engine/being-age.mjs +2 -7
  41. package/engine/being-measurements.mjs +12 -1
  42. package/engine/calendar-artifacts.mjs +6 -0
  43. package/engine/calendar-choice.mjs +12 -1
  44. package/engine/calendar-format.mjs +12 -1
  45. package/engine/calendar-human.mjs +12 -1
  46. package/engine/compile-corpus.mjs +3 -1
  47. package/engine/content-address.mjs +9 -12
  48. package/engine/content-alerts.mjs +13 -1
  49. package/engine/content-blocks.mjs +14 -3
  50. package/engine/content-embeds.mjs +11 -3
  51. package/engine/content-figures.mjs +9 -0
  52. package/engine/content-footnotes.mjs +12 -1
  53. package/engine/content-format-check.mjs +11 -262
  54. package/engine/content-format.yaml +5 -2
  55. package/engine/content-images.mjs +3 -0
  56. package/engine/content-index.mjs +443 -20
  57. package/engine/content-links.mjs +29 -35
  58. package/engine/content-lint.mjs +2 -3
  59. package/engine/content-poetry.mjs +12 -1
  60. package/engine/content-slug.mjs +2 -14
  61. package/engine/content-spans.mjs +13 -1
  62. package/engine/content-tables.mjs +12 -1
  63. package/engine/culture-choice.mjs +210 -13
  64. package/engine/data-keys.mjs +19 -16
  65. package/engine/date-conversion.mjs +12 -1
  66. package/engine/declaration-check.mjs +9 -0
  67. package/engine/derived-fields.mjs +6 -7
  68. package/engine/diagnostics.mjs +34 -4
  69. package/engine/docs-checks.mjs +9 -0
  70. package/engine/document-subtypes.mjs +47 -34
  71. package/engine/event-views.mjs +139 -14
  72. package/engine/exported-scene.mjs +92 -28
  73. package/engine/extension-attributes.mjs +12 -1
  74. package/engine/field-spec.mjs +11 -102
  75. package/engine/folder-notes.mjs +0 -64
  76. package/engine/foreign-catalog.mjs +2 -1
  77. package/engine/format-generated.mjs +9 -0
  78. package/engine/foundry-entries.mjs +10 -28
  79. package/engine/frontmatter-lint.mjs +83 -392
  80. package/engine/frontmatter.mjs +20 -70
  81. package/engine/generate.mjs +10 -15
  82. package/engine/heading-attributes.mjs +12 -1
  83. package/engine/helpers.mjs +41 -102
  84. package/engine/holdings.mjs +1 -1
  85. package/engine/homepage.mjs +3 -10
  86. package/engine/ids.mjs +1 -167
  87. package/engine/index-records.mjs +6 -4
  88. package/engine/index.mjs +0 -3
  89. package/engine/infobox-render.mjs +15 -3
  90. package/engine/infobox.mjs +13 -14
  91. package/engine/item-compiler.mjs +4 -10
  92. package/engine/item-docs.mjs +3 -5
  93. package/engine/item-registry.mjs +2 -10
  94. package/engine/journals.mjs +1 -5
  95. package/engine/literature-notes.mjs +4 -12
  96. package/engine/macros.mjs +1 -2
  97. package/engine/map-graphviz.mjs +12 -1
  98. package/engine/markdown-expressions.mjs +12 -1
  99. package/engine/markdownlint-run.mjs +567 -0
  100. package/engine/metadata-index.mjs +42 -12
  101. package/engine/note-addresses.mjs +143 -35
  102. package/engine/note-claims.mjs +14 -28
  103. package/engine/note-dates.mjs +3 -3
  104. package/engine/note-frontmatter.mjs +6 -0
  105. package/engine/note-ids.mjs +8 -14
  106. package/engine/note-package.mjs +8 -25
  107. package/engine/note-state.mjs +1 -3
  108. package/engine/note-vocabulary.mjs +148 -84
  109. package/engine/number-words.mjs +12 -1
  110. package/engine/pack-config.mjs +15 -25
  111. package/engine/pack-router.mjs +10 -16
  112. package/engine/pathnames.mjs +8 -22
  113. package/engine/pdf-build.mjs +773 -121
  114. package/engine/pdf-coverage.mjs +188 -0
  115. package/engine/pdf-index.mjs +54 -7
  116. package/engine/pdf-pages.mjs +247 -0
  117. package/engine/pdf-render.mjs +114 -4
  118. package/engine/pdf-toc.mjs +29 -32
  119. package/engine/project-init.mjs +46 -6
  120. package/engine/prose-config.mjs +1 -1
  121. package/engine/prose-lint.mjs +52 -88
  122. package/engine/readability-lint.mjs +9 -0
  123. package/engine/reckoning-markers.mjs +12 -1
  124. package/engine/reference-targets.mjs +7 -0
  125. package/engine/runtime-only-fields.mjs +5 -8
  126. package/engine/scene-levels.mjs +2 -2
  127. package/engine/scenes.mjs +1 -1
  128. package/engine/shortcode-references.mjs +7 -0
  129. package/engine/site-build.mjs +30 -16
  130. package/engine/site-index.mjs +10 -16
  131. package/engine/site-preview.mjs +6 -0
  132. package/engine/site-root.mjs +8 -1
  133. package/engine/social-tie-terms.mjs +12 -1
  134. package/engine/social-ties.mjs +3 -0
  135. package/engine/sql-tables.mjs +56 -20
  136. package/engine/standing-terms.mjs +11 -1
  137. package/engine/standings.mjs +11 -27
  138. package/engine/system-block.mjs +39 -141
  139. package/engine/system-vocabulary.mjs +6 -0
  140. package/engine/walk-files.mjs +69 -0
  141. package/engine/web-wikilinks.mjs +23 -3
  142. package/engine/wikilink-syntax.mjs +32 -21
  143. package/engine/wikilinks.mjs +27 -16
  144. package/githooks/attribution.mjs +8 -0
  145. package/githooks/commit-message.mjs +9 -0
  146. package/hm3/actors.mjs +9 -48
  147. package/hm3/default-item-art.mjs +9 -9
  148. package/hm3/item-fields.mjs +2 -9
  149. package/hm3/template-priority.mjs +6 -7
  150. package/hugo-theme/data/banner-kinds.yaml +78 -0
  151. package/hugo-theme/data/banners.yaml +2 -3
  152. package/hugo-theme/layouts/404.html +3 -3
  153. package/hugo-theme/layouts/partials/banner-url.html +31 -7
  154. package/hugo-theme/layouts/partials/hero-banner.html +13 -14
  155. package/hugo-theme/layouts/partials/related.html +1 -1
  156. package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
  157. package/hugo-theme/static/css/style.css +1 -1
  158. package/manifest.mjs +11 -4
  159. package/markdownlint-config.mjs +1 -1
  160. package/package.json +6 -3
  161. package/release.mjs +21 -14
  162. package/sohl/actors.mjs +16 -27
  163. package/sohl/default-item-art.mjs +3 -3
  164. package/sohl/document-subtypes.mjs +1 -16
  165. package/sohl/infobox.mjs +2 -3
  166. package/sohl/item-builders.mjs +5 -8
  167. package/sohl/item-fields.mjs +5 -35
  168. package/sohl/items.mjs +0 -4
  169. package/sohl/kb-passes.mjs +3 -3
  170. package/types/container.d.mts +116 -5
  171. package/types/content-config.d.mts +83 -35
  172. package/types/deploy.d.mts +42 -6
  173. package/types/e2e.d.mts +35 -3
  174. package/types/engine/address-charset.d.mts +4 -17
  175. package/types/engine/address.d.mts +17 -8
  176. package/types/engine/art-fields.d.mts +7 -4
  177. package/types/engine/art-slots.d.mts +6 -0
  178. package/types/engine/base-compiler.d.mts +15 -17
  179. package/types/engine/content-address.d.mts +9 -12
  180. package/types/engine/content-embeds.d.mts +4 -2
  181. package/types/engine/content-format-check.d.mts +0 -64
  182. package/types/engine/content-index.d.mts +82 -3
  183. package/types/engine/content-links.d.mts +2 -4
  184. package/types/engine/culture-choice.d.mts +51 -8
  185. package/types/engine/data-keys.d.mts +2 -2
  186. package/types/engine/derived-fields.d.mts +1 -2
  187. package/types/engine/diagnostics.d.mts +27 -4
  188. package/types/engine/document-subtypes.d.mts +12 -31
  189. package/types/engine/event-views.d.mts +14 -2
  190. package/types/engine/exported-scene.d.mts +3 -2
  191. package/types/engine/field-spec.d.mts +13 -122
  192. package/types/engine/folder-notes.d.mts +0 -39
  193. package/types/engine/foreign-catalog.d.mts +2 -1
  194. package/types/engine/foundry-entries.d.mts +4 -5
  195. package/types/engine/frontmatter-lint.d.mts +2 -2
  196. package/types/engine/frontmatter.d.mts +19 -45
  197. package/types/engine/generate.d.mts +1 -1
  198. package/types/engine/helpers.d.mts +25 -42
  199. package/types/engine/homepage.d.mts +3 -10
  200. package/types/engine/ids.d.mts +0 -114
  201. package/types/engine/index-records.d.mts +6 -4
  202. package/types/engine/index.d.mts +0 -1
  203. package/types/engine/infobox-render.d.mts +7 -1
  204. package/types/engine/infobox.d.mts +3 -2
  205. package/types/engine/item-docs.d.mts +1 -1
  206. package/types/engine/item-registry.d.mts +2 -4
  207. package/types/engine/literature-notes.d.mts +1 -1
  208. package/types/engine/markdownlint-run.d.mts +40 -0
  209. package/types/engine/metadata-index.d.mts +9 -1
  210. package/types/engine/note-addresses.d.mts +27 -2
  211. package/types/engine/note-claims.d.mts +3 -5
  212. package/types/engine/note-ids.d.mts +2 -4
  213. package/types/engine/note-package.d.mts +1 -1
  214. package/types/engine/note-state.d.mts +1 -3
  215. package/types/engine/note-vocabulary.d.mts +28 -21
  216. package/types/engine/pack-config.d.mts +2 -6
  217. package/types/engine/pack-router.d.mts +4 -4
  218. package/types/engine/pathnames.d.mts +3 -3
  219. package/types/engine/pdf-build.d.mts +98 -12
  220. package/types/engine/pdf-coverage.d.mts +60 -0
  221. package/types/engine/pdf-index.d.mts +34 -5
  222. package/types/engine/pdf-pages.d.mts +143 -0
  223. package/types/engine/pdf-render.d.mts +45 -1
  224. package/types/engine/pdf-toc.d.mts +12 -14
  225. package/types/engine/prose-config.d.mts +1 -1
  226. package/types/engine/prose-lint.d.mts +11 -10
  227. package/types/engine/runtime-only-fields.d.mts +2 -2
  228. package/types/engine/site-build.d.mts +9 -5
  229. package/types/engine/site-index.d.mts +4 -5
  230. package/types/engine/site-root.d.mts +1 -1
  231. package/types/engine/sql-tables.d.mts +31 -2
  232. package/types/engine/standing-terms.d.mts +2 -1
  233. package/types/engine/standings.d.mts +7 -9
  234. package/types/engine/system-block.d.mts +10 -44
  235. package/types/engine/walk-files.d.mts +15 -0
  236. package/types/engine/wikilink-syntax.d.mts +18 -19
  237. package/types/hm3/actors.d.mts +1 -2
  238. package/types/hm3/item-fields.d.mts +2 -4
  239. package/types/hm3/template-priority.d.mts +6 -7
  240. package/types/manifest.d.mts +3 -2
  241. package/types/release.d.mts +8 -7
  242. package/types/sohl/actors.d.mts +2 -1
  243. package/types/sohl/document-subtypes.d.mts +1 -2
  244. package/types/sohl/item-fields.d.mts +3 -7
  245. package/engine/retired-fields.mjs +0 -631
  246. package/types/engine/retired-fields.d.mts +0 -354
@@ -15,8 +15,8 @@
15
15
  * The things the content format asserts that can be checked.
16
16
  *
17
17
  * `content-format.mjs` reads the specification as data; this compares it
18
- * against the three worlds it makes claims about — the systems it maps onto,
19
- * the notes it governs, and the field declarations that compile them.
18
+ * against the two worlds it makes claims about — the systems it maps onto and
19
+ * the field declarations that compile them.
20
20
  *
21
21
  * ## The specification against a system's published schema
22
22
  *
@@ -40,31 +40,6 @@
40
40
  * this whole module avoids. So the question asked here is "does any schema
41
41
  * declare this field?", and it narrows to the subtype once the map lands.
42
42
  *
43
- * ## The corpus against the declared vocabulary
44
- *
45
- * Every authored note is measured against the per-type `data` tables. Three
46
- * classes of finding come out of them, one per slice of the migration:
47
- *
48
- * | class | what it means |
49
- * | --- | --- |
50
- * | `unknown-type` | the format declares no section for this note's `type` |
51
- * | `unknown-data-key` | a key in `data:`, which is closed, that the type does not declare |
52
- * | `top-level-data-key` | a declared `data` property written at top level instead |
53
- * | `system-block-data-key` | a declared shared source written straight into a system block |
54
- *
55
- * **It reports; it does not fail.** All ~6,210 authored notes predate the
56
- * format, so a failing check would be red on day one in every repository and
57
- * would stay red for the length of the epic — which is a check nobody can act
58
- * on and everybody learns to skip. The counts are the migration's progress bar
59
- * instead, and `--strict` turns them fatal — turned on slice by slice, as each
60
- * class reaches zero.
61
- *
62
- * **What it deliberately does not check.** A key inside a `sohl:` or `hm3:`
63
- * block that the format says nothing about is left alone: those regions are
64
- * closed against *the system's* schema, not against this document, and
65
- * `frontmatter-lint.mjs` already checks them against the declared fields. This
66
- * module only reports a key whose home the format actually states.
67
- *
68
43
  * ## The specification against the declarations that compile it
69
44
  *
70
45
  * The specification hand-writes a `data` table under most of its type sections,
@@ -88,9 +63,8 @@
88
63
  * set and are not meant to be: the document names the *shared* source a field is
89
64
  * written as, while a declaration names every key the system's own block accepts
90
65
  * — including the system-specific ones (`heft`, `strikeModes`) that the document
91
- * correctly never maps. Until the corpus has moved into `data:`, holding the
92
- * sets equal would report the migration itself as a defect on every run. So the
93
- * fields only one side names come back as *coverage*, and the types only one side
66
+ * correctly never maps. Holding the sets equal would report a defect where
67
+ * there is none. So the fields only one side names come back as *coverage*, and the types only one side
94
68
  * describes come back **named** rather than skipped in silence — a check that
95
69
  * quietly compared nine of twenty-three types would read as one that covered
96
70
  * them all.
@@ -98,15 +72,9 @@
98
72
  * @module
99
73
  */
100
74
 
101
- import { currentType } from "./ids.mjs";
102
75
  import { SCHEMA_ARTIFACT_VERSION } from "./schema-check.mjs";
103
76
  import { authoredFields } from "./field-spec.mjs";
104
- import { positionInFrontmatter } from "./diagnostics.mjs";
105
- import { RETIRED_FIELD_ALIASES } from "./retired-fields.mjs";
106
77
  import { authoredKey } from "./system-block.mjs";
107
- // The top-level vocabulary, read rather than restated — a second list of the
108
- // keys a note's own identity uses is one more thing to drift.
109
- import { NOTE_TOP_LEVEL_KEY_SET } from "./note-vocabulary.mjs";
110
78
 
111
79
  /**
112
80
  * Every field path any subtype of a published schema declares.
@@ -225,223 +193,6 @@ export function checkSchemaTargets({ format, schemas }) {
225
193
  return { findings, checked, unchecked };
226
194
  }
227
195
 
228
- /**
229
- * Edit distance, capped — enough to answer "did you mean".
230
- *
231
- * The same arithmetic `frontmatter-lint.mjs` carries. Duplicated rather than
232
- * shared because that module keeps it private and this one is the second
233
- * caller; the third is the moment to lift it out, not the second.
234
- *
235
- * @param {string} a - One string.
236
- * @param {string} b - The other.
237
- * @returns {number} The Levenshtein distance.
238
- */
239
- function distance(a, b) {
240
- const cols = b.length + 1;
241
- let prev = Array.from({ length: cols }, (_, j) => j);
242
- for (let i = 1; i <= a.length; i += 1) {
243
- const row = [i];
244
- for (let j = 1; j < cols; j += 1) {
245
- row[j] = Math.min(
246
- prev[j] + 1,
247
- row[j - 1] + 1,
248
- prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
249
- );
250
- }
251
- prev = row;
252
- }
253
- return prev[cols - 1];
254
- }
255
-
256
- /**
257
- * The declared key an unknown one was most likely meant to be.
258
- *
259
- * @param {string} key - The unknown key.
260
- * @param {Iterable<string>} candidates - The declared keys.
261
- * @returns {string|undefined} The nearest, when it is near enough to suggest.
262
- */
263
- function nearest(key, candidates) {
264
- let best;
265
- let bestAt = Infinity;
266
- for (const candidate of candidates) {
267
- const d = distance(key.toLowerCase(), candidate.toLowerCase());
268
- if (d < bestAt) {
269
- bestAt = d;
270
- best = candidate;
271
- }
272
- }
273
- return bestAt <= Math.max(1, Math.floor(key.length / 3)) ? best : undefined;
274
- }
275
-
276
- /**
277
- * Top-level keys the format names as the note's own, whatever its type.
278
- *
279
- * The top-level vocabulary itself, read rather than restated: this set exists
280
- * to keep a note's own identity fields from being mistaken for a misplaced
281
- * `data` property where the two share a name, and the keys that are a note's
282
- * own are exactly the ones the region declares. `type` and `subType` are the
283
- * pair that matters — several mapping tables name `subType` as a shared source,
284
- * and it is authored at top level by design. A key the region does *not*
285
- * declare, such as `id` beside `data.id`, is reported as the misplaced `data`
286
- * property it is.
287
- *
288
- * @type {ReadonlySet<string>}
289
- */
290
- export const NOTE_LEVEL_KEYS = NOTE_TOP_LEVEL_KEY_SET;
291
-
292
- /** Whether a value is a plain object a block could be written as. */
293
- function isBlock(value) {
294
- return typeof value === "object" && value !== null && !Array.isArray(value);
295
- }
296
-
297
- /** @type {WeakMap<object, ReadonlySet<string>>} */
298
- const systemCache = new WeakMap();
299
-
300
- /**
301
- * The systems the specification names.
302
- *
303
- * Taken from the columns of its mapping tables rather than from a list written
304
- * here, so a system added to the document is measured with no code change.
305
- * Cached per parsed document, since the answer is the same for every note.
306
- *
307
- * @param {import("./content-format.mjs").ContentFormat} format - The parsed
308
- * specification.
309
- * @returns {ReadonlySet<string>} The system ids.
310
- */
311
- export function systemsNamed(format) {
312
- let known = systemCache.get(format);
313
- if (!known) {
314
- known = new Set(format.claims.map((claim) => claim.system));
315
- systemCache.set(format, known);
316
- }
317
- return known;
318
- }
319
-
320
- /**
321
- * Measure one note against the vocabulary the format declares for its type.
322
- *
323
- * @param {object} note - `{file, raw, fm}` — as the link index hands one over.
324
- * @param {import("./content-format.mjs").ContentFormat} format - The parsed
325
- * specification.
326
- * @param {object} [opts]
327
- * @param {"warning"|"error"} [opts.severity="warning"] - What the findings are.
328
- * A report by default; `--strict` raises it.
329
- * @returns {object[]} Findings, each carrying the `class` it belongs to
330
- * alongside the fields `emitDiagnostic` reads.
331
- */
332
- export function measureNote(note, format, { severity = "warning" } = {}) {
333
- const findings = [];
334
- const fm = note?.fm ?? {};
335
- const type = String(fm.type ?? "");
336
- const raw = () => note?.raw ?? "";
337
- const at = (key, value) => positionInFrontmatter(raw(), key, value);
338
- const add = (cls, key, message, value) =>
339
- findings.push({
340
- file: note?.file,
341
- ...at(key, value),
342
- severity,
343
- class: cls,
344
- message,
345
- });
346
-
347
- // The specification's sections are headed by the current spelling of a note
348
- // type, so a note still on a renamed one is measured against the section it
349
- // will move to rather than reported as a type the format does not declare.
350
- // The rename itself is the frontmatter lint's finding.
351
- const spec = format.types.get(currentType(type));
352
- if (!spec) {
353
- add(
354
- "unknown-type",
355
- "type",
356
- `the content format declares no \`### type: ${type}\` section, so ` +
357
- `nothing says what this note may write`,
358
- type,
359
- );
360
- return findings;
361
- }
362
-
363
- // `data:` is closed: every key it carries must be one the type declares.
364
- const data = isBlock(fm.data) ? fm.data : undefined;
365
- // The retired spelling of a key the format states is measured as the key it
366
- // will be written as, exactly as a note still on a renamed *type* is
367
- // measured against the section it will move to. The rename itself is the
368
- // frontmatter lint's finding, and reporting it here as well would count one
369
- // note twice in a migration whose counts are the point.
370
- const renamedDataKeys = new Set(
371
- [...spec.dataKeys].map((key) => RETIRED_FIELD_ALIASES[key]).filter(Boolean),
372
- );
373
- for (const key of Object.keys(data ?? {})) {
374
- if (spec.dataKeys.has(key) || renamedDataKeys.has(key)) continue;
375
- const guess = nearest(key, spec.dataKeys);
376
- add(
377
- "unknown-data-key",
378
- key,
379
- `\`data.${key}\` is not a property of a ${type}` +
380
- (guess ? `. Did you mean "${guess}"?` : ""),
381
- );
382
- }
383
-
384
- // The two regions a shared source is written in today, and neither is where
385
- // the format puts it. A key the type's own table declares is not merely an
386
- // unrecognised one — it is one whose home the format states, so the finding
387
- // names that home rather than the vocabulary.
388
- for (const key of spec.dataKeys) {
389
- if (NOTE_LEVEL_KEYS.has(key)) continue;
390
- if (Object.hasOwn(fm, key)) {
391
- add(
392
- "top-level-data-key",
393
- key,
394
- `\`${key}\` is a declared property of a ${type} and belongs in ` +
395
- `\`data.${key}\`; at top level it reaches the web page and no ` +
396
- `Foundry document`,
397
- );
398
- }
399
- for (const system of systemsNamed(format)) {
400
- const block = fm[system];
401
- if (!isBlock(block) || !Object.hasOwn(block, key)) continue;
402
- add(
403
- "system-block-data-key",
404
- key,
405
- `\`${system}.${key}\` is a declared shared source and belongs in ` +
406
- `\`data.${key}\`; a system states an exception under ` +
407
- `\`${system}.system\`, not by holding the shared value itself`,
408
- );
409
- }
410
- }
411
-
412
- return findings;
413
- }
414
-
415
- /**
416
- * Measure a corpus, and count what it finds by class.
417
- *
418
- * The counts are the point as much as the findings: a class is promoted to
419
- * fatal when its count reaches zero, so a run that prints them is the
420
- * migration's progress bar.
421
- *
422
- * @param {Iterable<object>} notes - `{file, raw, fm}` for each authored note.
423
- * @param {import("./content-format.mjs").ContentFormat} format - The parsed
424
- * specification.
425
- * @param {object} [opts]
426
- * @param {boolean} [opts.strict=false] - Report the findings as errors rather
427
- * than warnings. Turned on one slice at a time.
428
- * @returns {{findings: object[], notes: number, byClass: Record<string, number>}}
429
- */
430
- export function measureCorpus(notes, format, { strict = false } = {}) {
431
- const findings = [];
432
- let count = 0;
433
- for (const note of notes) {
434
- count += 1;
435
- findings.push(...measureNote(note, format, { severity: strict ? "error" : "warning" }));
436
- }
437
- /** @type {Record<string, number>} */
438
- const byClass = {};
439
- for (const finding of findings) {
440
- byClass[finding.class] = (byClass[finding.class] ?? 0) + 1;
441
- }
442
- return { findings, notes: count, byClass };
443
- }
444
-
445
196
  /**
446
197
  * Where one declared field sends one authored value.
447
198
  *
@@ -487,12 +238,11 @@ export function fieldDriftMessage({ noteType, source, target, name, to }) {
487
238
  * A shared source, as the two sides spell it.
488
239
  *
489
240
  * The specification writes every type-specific row `data.<key>` — the container
490
- * `data:` put those facts in — while a declaration writes either: the
491
- * bare key it has always named, or the same dotted path now that a field can
492
- * declare its shared source and its legacy in-block key separately.
493
- * Both are the same source, so both are normalized before they are compared;
494
- * matching one spelling against the other would report every moved field as
495
- * unmapped, which is the opposite of what this check is for.
241
+ * `data:` holds those facts in — while a declaration writes either the bare
242
+ * key or the same dotted path. Both are the same source, so both are
243
+ * normalized before they are compared; matching one spelling against the
244
+ * other would report every field as unmapped, which is the opposite of what
245
+ * this check is for.
496
246
  *
497
247
  * A note-level source — the `subType` several tables name — carries no prefix
498
248
  * and is returned unchanged.
@@ -600,9 +350,8 @@ export function checkDeclaredFields({ format, itemFields, system, severity = "er
600
350
  const target = claim.target.replace(/^system\./, "");
601
351
  const found = declarationFor(claim.source, authored);
602
352
  // No declaration names it. That is coverage, not a contradiction —
603
- // the specification maps fields no builder emits yet, which is the
604
- // ordinary mid-migration state and what `schema-check.mjs`
605
- // already reports as unemitted.
353
+ // the specification maps fields no builder emits, which
354
+ // `schema-check.mjs` already reports as unemitted.
606
355
  if (!found) continue;
607
356
  const match = found.field;
608
357
  fields += 1;
@@ -7,7 +7,6 @@ types:
7
7
  - templatePriority
8
8
  - archetypes
9
9
  - occupation
10
- - stations
11
10
  - lore
12
11
  - culture
13
12
  - homes
@@ -56,6 +55,7 @@ types:
56
55
  - economy
57
56
  - lore
58
57
  - parents
58
+ - culture
59
59
  - relations
60
60
  subTypes:
61
61
  - guild
@@ -279,6 +279,7 @@ types:
279
279
  data:
280
280
  - scene
281
281
  - fixup
282
+ - thumbnail
282
283
  - place
283
284
  subTypes:
284
285
  - battlemap
@@ -293,6 +294,7 @@ types:
293
294
  - purpose
294
295
  - lore
295
296
  - parents
297
+ - culture
296
298
  - population
297
299
  - government
298
300
  - market
@@ -336,7 +338,8 @@ types:
336
338
  - adventure
337
339
  - encounter
338
340
  doc:
339
- data: []
341
+ data:
342
+ - culture
340
343
  subTypes:
341
344
  - rules
342
345
  - userguide
@@ -5,6 +5,9 @@
5
5
  * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
6
  * You may copy, modify, and distribute it under the terms of that license.
7
7
  *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
8
11
  * SPDX-License-Identifier: GPL-3.0-or-later
9
12
  */
10
13