@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
@@ -298,14 +298,14 @@ export class BasePackCompiler {
298
298
  * A refusal only this pass can make, because its subject is the note's
299
299
  * **type**.
300
300
  *
301
- * The `assertNo*Field` family above it in the walk is type-agnostic by
302
- * construction: it runs before `selects`, so that a note declaring a
303
- * retired field is answered whichever pass would have claimed it. A rule
301
+ * The `assertNoDeclaredPackage` check above it in the walk is type-agnostic
302
+ * by construction: it runs before `selects`, so that a note declaring
303
+ * `package:` is answered whichever pass would have claimed it. A rule
304
304
  * about what a *`trauma`* may write cannot live there — it needs the type's
305
305
  * field declaration, which only the pass that compiles the type can reach.
306
306
  *
307
307
  * So it is a hook, called once the note is known to be this pass's, and its
308
- * throw is counted and located exactly as the family's is: the note is
308
+ * throw is counted and located exactly as that check's is: the note is
309
309
  * declined rather than skipped, and the build fails naming the line.
310
310
  *
311
311
  * The default refuses nothing, which is the honest position for a pass
@@ -404,12 +404,10 @@ export class BasePackCompiler {
404
404
  /**
405
405
  * The Foundry path one authored art address names, or `null`.
406
406
  *
407
- * **The two empties survive.** `null` and an absent key mean *no art named*,
408
- * so the caller's own default applies with nullish coalescing; `""` means
409
- * *ship blank on purpose* and no default may replace it. That is
410
- * {@link module:engine/helpers.resolveImg}'s rule, reached through it rather
411
- * than restated, which is also what puts an art address and a body image
412
- * through one ownership rule.
407
+ * `null` and an absent key mean *no art named*, so the caller's own default
408
+ * applies with nullish coalescing. The address goes through
409
+ * {@link module:engine/helpers.resolveImg}, which puts an art address and a
410
+ * body image through one ownership rule.
413
411
  *
414
412
  * An address nothing answers is reported against the note and treated as
415
413
  * unnamed, so the document takes its default rather than shipping a path
@@ -421,8 +419,8 @@ export class BasePackCompiler {
421
419
  * @param {string} key - The key it was authored at, for the message.
422
420
  * @param {string} type - The asset type a bare value takes.
423
421
  * @param {Iterable<string>} [accepts] - The types this slot accepts.
424
- * @returns {string|null} The Foundry-relative path, `""` for a deliberate
425
- * blank, or `null` where the note names none.
422
+ * @returns {string|null} The Foundry-relative path, or `null` where the
423
+ * note names none.
426
424
  */
427
425
  artPathOf(value: unknown, key: string, type: string, accepts?: Iterable<string>): string | null;
428
426
  /**
@@ -636,8 +634,8 @@ export class BasePackCompiler {
636
634
  * The tallies one pass accumulates while walking the tree.
637
635
  *
638
636
  * `declined` and `skippedOther` are deliberately separate numbers. A declined
639
- * note is one this build **refused** — it declares a retired frontmatter field
640
- * — and it is an error; a skipped one legitimately belongs to another pass, and
637
+ * note is one this build **refused** — it declares `package:` — and it is an
638
+ * error; a skipped one legitimately belongs to another pass, and
641
639
  * there are thousands of those. Folding the first into the second is what let a
642
640
  * whole tree be filtered out in silence.
643
641
  */
@@ -655,9 +653,9 @@ export type PassStats = {
655
653
  */
656
654
  skippedOther: number;
657
655
  /**
658
- * - Notes this pack **refused** — one declaring a
659
- * retired frontmatter field, or one routed to a system pack whose system it
660
- * says nothing about. Counted as errors, never as skips.
656
+ * - Notes this pack **refused** — one declaring
657
+ * `package:`, or one routed to a system pack whose system it says nothing
658
+ * about. Counted as errors, never as skips.
661
659
  */
662
660
  declined: number;
663
661
  };
@@ -49,12 +49,10 @@ export function addressSlug(fm: object): string;
49
49
  * docs, neither of which contains a hyphen or names a type.
50
50
  *
51
51
  * **It is a pure function of the frontmatter**, and takes no options. Nothing
52
- * about the file the note was read from reaches it: the `README.md` convention
53
- * that made one note address a whole section is retired with the section itself,
54
- * so every note is addressed alike and there is one rule and no branch.
55
- * It takes no address scheme; the `landing` rule it once validated is
56
- * discarded; with that key retired, `prefix` was the only thing left in the
57
- * scheme and the paragraph above is the reason it never applied.
52
+ * about the file the note was read from reaches it, so every note is addressed
53
+ * alike and there is one rule and no branch. It takes no address scheme:
54
+ * `prefix` is the whole scheme and the paragraph above is the reason it never
55
+ * applies here.
58
56
  *
59
57
  * @param {object} fm - Parsed frontmatter.
60
58
  * @returns {string} The package-relative address, with a trailing slash and no
@@ -124,13 +122,12 @@ export function resolvePackageUrl(rel: string, base: string): string;
124
122
  * The Foundry `_id` of the document a note compiles into, derived from its
125
123
  * canonical address.
126
124
  *
127
- * A note used to author this — an opaque 16-character string, one per note,
128
- * that said nothing its address did not and that no check guaranteed. The
129
- * address is the identity that *is* guaranteed: `content-lint` refuses a
125
+ * An authored id would be an opaque string that says nothing its address
126
+ * does not and that no check guarantees. The address is the identity that
127
+ * *is* guaranteed: `content-lint` refuses a
130
128
  * duplicate `(type, shortcode)` across every pack of a document type, which is
131
129
  * exactly the scope a primary document's id must be unique within. So the
132
- * derived id inherits a guard that already exists, where the authored one had
133
- * none.
130
+ * derived id inherits that guard.
134
131
  *
135
132
  * **The coupling this creates, stated plainly.** The address carries the
136
133
  * shortcode, so renaming a shortcode moves the document's id — where an
@@ -139,7 +136,7 @@ export function resolvePackageUrl(rel: string, base: string): string;
139
136
  * note, so it is a breaking change either way; and a note that must keep its
140
137
  * identity across a rename pins an `id`, which is what the pin is for. One
141
138
  * thing genuinely degrades — `engine/address-diff.mjs` tells a rename from a
142
- * withdrawal by matching document ids, and can no longer do so for a note that
139
+ * withdrawal by matching document ids, and cannot do so for a note that
143
140
  * authors none. Its module note records that.
144
141
  *
145
142
  * @param {string} pkg - The owning **content** package.
@@ -35,8 +35,8 @@ export function embedsIn(body: string, context: any): ParsedEmbed[];
35
35
  * @param {object} index - From {@link module:engine/wikilinks.buildWikilinkIndex},
36
36
  * or the equivalent the site and the book build.
37
37
  * @param {ParsedEmbed} embed - The embed.
38
- * @returns {{pathname: string}|{reason: string, target: string, type?: string}}
39
- * The authored pathname the picture is at, or the finding.
38
+ * @returns {{pathname: string}|{reason: string, target: string, type?: string,
39
+ * shortcode?: string, declared?: string}} The authored pathname the picture is at, or the finding.
40
40
  */
41
41
  export function resolveEmbed(index: object, embed: ParsedEmbed): {
42
42
  pathname: string;
@@ -44,6 +44,8 @@ export function resolveEmbed(index: object, embed: ParsedEmbed): {
44
44
  reason: string;
45
45
  target: string;
46
46
  type?: string;
47
+ shortcode?: string;
48
+ declared?: string;
47
49
  };
48
50
  /**
49
51
  * The role an address's asset declares, read through the same resolution an
@@ -56,55 +56,6 @@ export function checkSchemaTargets({ format, schemas }: {
56
56
  checked: number;
57
57
  unchecked: Record<string, number>;
58
58
  };
59
- /**
60
- * The systems the specification names.
61
- *
62
- * Taken from the columns of its mapping tables rather than from a list written
63
- * here, so a system added to the document is measured with no code change.
64
- * Cached per parsed document, since the answer is the same for every note.
65
- *
66
- * @param {import("./content-format.mjs").ContentFormat} format - The parsed
67
- * specification.
68
- * @returns {ReadonlySet<string>} The system ids.
69
- */
70
- export function systemsNamed(format: import("./content-format.mjs").ContentFormat): ReadonlySet<string>;
71
- /**
72
- * Measure one note against the vocabulary the format declares for its type.
73
- *
74
- * @param {object} note - `{file, raw, fm}` — as the link index hands one over.
75
- * @param {import("./content-format.mjs").ContentFormat} format - The parsed
76
- * specification.
77
- * @param {object} [opts]
78
- * @param {"warning"|"error"} [opts.severity="warning"] - What the findings are.
79
- * A report by default; `--strict` raises it.
80
- * @returns {object[]} Findings, each carrying the `class` it belongs to
81
- * alongside the fields `emitDiagnostic` reads.
82
- */
83
- export function measureNote(note: object, format: import("./content-format.mjs").ContentFormat, { severity }?: {
84
- severity?: "error" | "warning" | undefined;
85
- }): object[];
86
- /**
87
- * Measure a corpus, and count what it finds by class.
88
- *
89
- * The counts are the point as much as the findings: a class is promoted to
90
- * fatal when its count reaches zero, so a run that prints them is the
91
- * migration's progress bar.
92
- *
93
- * @param {Iterable<object>} notes - `{file, raw, fm}` for each authored note.
94
- * @param {import("./content-format.mjs").ContentFormat} format - The parsed
95
- * specification.
96
- * @param {object} [opts]
97
- * @param {boolean} [opts.strict=false] - Report the findings as errors rather
98
- * than warnings. Turned on one slice at a time.
99
- * @returns {{findings: object[], notes: number, byClass: Record<string, number>}}
100
- */
101
- export function measureCorpus(notes: Iterable<object>, format: import("./content-format.mjs").ContentFormat, { strict }?: {
102
- strict?: boolean | undefined;
103
- }): {
104
- findings: object[];
105
- notes: number;
106
- byClass: Record<string, number>;
107
- };
108
59
  /**
109
60
  * What an author is told when the specification and the declaration disagree.
110
61
  *
@@ -177,21 +128,6 @@ export function checkDeclaredFields({ format, itemFields, system, severity }: {
177
128
  };
178
129
  fields: number;
179
130
  };
180
- /**
181
- * Top-level keys the format names as the note's own, whatever its type.
182
- *
183
- * The top-level vocabulary itself, read rather than restated: this set exists
184
- * to keep a note's own identity fields from being mistaken for a misplaced
185
- * `data` property where the two share a name, and the keys that are a note's
186
- * own are exactly the ones the region declares. `type` and `subType` are the
187
- * pair that matters — several mapping tables name `subType` as a shared source,
188
- * and it is authored at top level by design. A key the region does *not*
189
- * declare, such as `id` beside `data.id`, is reported as the misplaced `data`
190
- * property it is.
191
- *
192
- * @type {ReadonlySet<string>}
193
- */
194
- export const NOTE_LEVEL_KEYS: ReadonlySet<string>;
195
131
  /**
196
132
  * Where one declared field sends one authored value.
197
133
  */
@@ -143,6 +143,62 @@ export function indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absP
143
143
  dateContext: any;
144
144
  resolveRole: any;
145
145
  }): object[];
146
+ /**
147
+ * Write each note's resolved culture into its record, as `data.culture`.
148
+ *
149
+ * A note's culture is:
150
+ *
151
+ * - its own `data.culture`, when it states one;
152
+ * - for a `lore` note of `subType: culture`, its own canonical Address;
153
+ * - for a `place` or an `affiliation`, else the resolved cultures of its
154
+ * `data.parents`;
155
+ * - for a `polity` affiliation, else the resolved cultures of its places: its
156
+ * `data.seat` pooled with every place whose `data.government` names it, as
157
+ * one set;
158
+ * - else nothing. A `being`, a `doc` and any other `lore` note have their own
159
+ * value only; every other type has none.
160
+ *
161
+ * A parent's culture is its own resolved culture, found recursively; a parent
162
+ * resolving to nothing is ignored, as is one in another package that its index
163
+ * gives none. Sources that agree give that culture. Sources that differ, on a
164
+ * note with no value of its own, give it none and one error finding, at
165
+ * `data.parents`, or for a polity's places at `data.seat`, else at its `data:`
166
+ * line. A note whose parent raised that error treats the parent as resolving to
167
+ * nothing, so a disagreement is reported once, where it is. A cycle resolves to
168
+ * nothing.
169
+ *
170
+ * A parent in another package is read through `foreignRecord`, whose published
171
+ * entry already carries that parent's resolved culture, so a foreign chain is
172
+ * never walked.
173
+ *
174
+ * The record's `data` is replaced by a copy carrying the culture, never
175
+ * assigned into, because the note's parsed frontmatter shares that object. The
176
+ * record then carries the resolved culture to every pass that reads it — lint,
177
+ * compile and site alike. A note resolving to nothing gains no `culture` key.
178
+ *
179
+ * @param {Array<Record<string, any>>} records - Index records, every Address a
180
+ * tuple. Records that are not notes are passed over.
181
+ * @param {object} options - Options.
182
+ * @param {string} options.contentPackage - The package the records belong to.
183
+ * @param {string} [options.contentBase] - The content tree, for opening a
184
+ * conflicting note to locate the finding. Without it a finding names the
185
+ * note's path within the tree and no position.
186
+ * @param {(key: string) => ({culture?: unknown}|undefined)} [options.foreignRecord] -
187
+ * Another package's entry for a canonical address. Asked only for an
188
+ * immediate parent whose package is not this one.
189
+ * @param {object[]} [options.problems] - Receives each conflict as a finding.
190
+ * Omitted, every conflict is collected and one Error lists them all.
191
+ * @returns {Array<Record<string, any>>} The same records.
192
+ * @throws {Error} When `problems` is omitted and any note's sources disagree.
193
+ */
194
+ export function resolveCultures(records: Array<Record<string, any>>, { contentPackage, contentBase, foreignRecord, problems }: {
195
+ contentPackage: string;
196
+ contentBase?: string | undefined;
197
+ foreignRecord?: ((key: string) => ({
198
+ culture?: unknown;
199
+ } | undefined)) | undefined;
200
+ problems?: object[] | undefined;
201
+ }): Array<Record<string, any>>;
146
202
  /**
147
203
  * Read a content tree into index records, in the order they will be written.
148
204
  *
@@ -171,17 +227,32 @@ export function indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absP
171
227
  * @param {object[]} [options.problems] - Supplied by a **reader**: a note that
172
228
  * cannot be recorded is pushed here as a diagnostic and skipped. Omitted, the
173
229
  * note throws — the contract the emitter needs, since an index missing a note
174
- * asserts that it does not exist.
230
+ * asserts that it does not exist. The same holds for a culture conflict: a
231
+ * reader receives each as a finding, and the emitter throws once, listing
232
+ * every one.
233
+ * @param {(key: string) => ({culture?: unknown}|undefined)} [options.foreignRecord] -
234
+ * Another package's entry for a canonical address, read for a parent that
235
+ * package publishes; see {@link resolveCultures}.
236
+ * @param {Map<string, {body: string, frontmatter: object|null}>} [options.parsed] -
237
+ * Filled with each note's body and its frontmatter as authored, keyed by the
238
+ * note's file, so a later pass reads the notes this walk already parsed.
175
239
  * @returns {Array<Record<string, any>>} The records, in a total order that does
176
240
  * not depend on directory-read order.
177
241
  */
178
- export function collectContentIndex(contentBase: string, { contentPackage, skipDirectories, assetsBase, manifest, problems, addressContext }: {
242
+ export function collectContentIndex(contentBase: string, { contentPackage, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, }: {
179
243
  contentPackage: string;
180
244
  skipDirectories: readonly string[];
181
245
  assetsBase?: string | undefined;
182
246
  manifest?: object | undefined;
183
247
  addressContext?: object | undefined;
184
248
  problems?: object[] | undefined;
249
+ foreignRecord?: ((key: string) => ({
250
+ culture?: unknown;
251
+ } | undefined)) | undefined;
252
+ parsed?: Map<string, {
253
+ body: string;
254
+ frontmatter: object | null;
255
+ }> | undefined;
185
256
  }): Array<Record<string, any>>;
186
257
  /**
187
258
  * Serialize records as JSON Lines.
@@ -218,14 +289,22 @@ export function serializeContentIndex(records: Array<Record<string, any>>): stri
218
289
  * aborting the derivation. Omitted, the note throws — which is the contract
219
290
  * the emitter needs, since an index missing a note asserts that it does not
220
291
  * exist.
292
+ * @param {Map<string, {body: string, frontmatter: object|null}>} [opts.parsed] -
293
+ * Filled with each note's body and authored frontmatter, keyed by the note's
294
+ * file. Handed to {@link module:engine/sql-tables.prepareTreeSqlTables}, it
295
+ * spares that pass reading and parsing every note again.
221
296
  * @returns {object[]} One record per note, plus one per documentation entry.
222
297
  */
223
- export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectories, problems, }?: {
298
+ export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectories, problems, parsed, }?: {
224
299
  contentBase?: string | undefined;
225
300
  assetsBase?: string | undefined;
226
301
  config?: object | undefined;
227
302
  skipDirectories?: readonly string[] | undefined;
228
303
  problems?: object[] | undefined;
304
+ parsed?: Map<string, {
305
+ body: string;
306
+ frontmatter: object | null;
307
+ }> | undefined;
229
308
  }): object[];
230
309
  /**
231
310
  * Emit this package's content index.
@@ -21,8 +21,8 @@ export function anchorsOf(body: string): Set<string>;
21
21
  * Read a content tree into the index a link resolves against.
22
22
  *
23
23
  * **The corpus comes from the content index, not from a walk of this module's
24
- * own**. Every pass used to answer "which files are the content?" for
25
- * itself and throw the answer away; this one now reads
24
+ * own**. Passes do not each answer "which files are the content?" for
25
+ * themselves; this one reads
26
26
  * {@link module:engine/content-index.indexRecordsFor}, which is the same
27
27
  * derivation the published artifact and the compilers are driven from. So a
28
28
  * note the index records is a note the link check sees, and the addresses and
@@ -81,8 +81,6 @@ export function buildLinkIndex(contentBase: string, { config, skipDirectories, s
81
81
  * **What is checkable, stated plainly.** Only an address into this site is, and
82
82
  * only against facts this build already holds:
83
83
  *
84
- * - A **retired content type** in the path. The engine knows the retired names
85
- * and what replaced it, so this is a fact rather than a guess.
86
84
  * - A **hardcoded absolute URL** into this package's own prefix, or into one a
87
85
  * fetched index names. Every one of them has a better form to write, which
88
86
  * is why every one is reported — including a bare `/<package>/`, which names
@@ -1,14 +1,57 @@
1
1
  /**
2
- * Require a being's primary culture to address a culture lore note.
2
+ * Whether a `data.culture` value names the note that carries it.
3
+ *
4
+ * The comparison is between canonical Addresses, so a culture note's shortcode,
5
+ * any longer form of its Address and a decoded tuple all name it alike. A note
6
+ * with no shortcode has no Address to compare against and names nothing.
7
+ *
8
+ * @param {object} fm - The note's frontmatter.
9
+ * @param {unknown} value - The `data.culture` value, as written or decoded.
10
+ * @param {object} [index] - The package and vocabularies a written value is
11
+ * read against, and the package the note's own Address is in.
12
+ * @returns {boolean} Whether the value is the note's own Address.
13
+ */
14
+ export function cultureNamesItself(fm: object, value: unknown, index?: object): boolean;
15
+ /**
16
+ * Require a note's `data.culture` to address a culture lore note.
17
+ *
18
+ * A value that is not a `lore` Address, or names nothing, is left to the
19
+ * reference check, which reports it in its own terms. A culture note's own
20
+ * value is {@link checkCultureSubType}'s to judge, since it names only the note
21
+ * itself.
22
+ *
3
23
  * @param {object} note
4
24
  * @param {{index?: object}} [options]
25
+ * @returns {object[]} One error at the value when it names lore of another subType.
5
26
  */
6
27
  export function checkCultureChoice(note: object, { index }?: {
7
28
  index?: object;
8
- }): {
9
- severity: string;
10
- message: string;
11
- line?: number;
12
- column?: number;
13
- file: any;
14
- }[];
29
+ }): object[];
30
+ /**
31
+ * Scope `data.culture` to the subtypes that take it.
32
+ *
33
+ * A `doc` of `subType: settingguide` introduces one culture's setting and must
34
+ * name it; a missing value is an error at the `subType:` line. Every other `doc`
35
+ * subType refuses a written value, as an error at the key.
36
+ *
37
+ * A `lore` note of `subType: culture` is itself the culture. It may omit the
38
+ * field or name itself, in any form the Address parser reads; a value naming any
39
+ * other Address is an error at the value. A note with no shortcode has no
40
+ * Address to compare against, and the shortcode check reports it.
41
+ *
42
+ * @param {object} note - The note, as the link index hands it over.
43
+ * @param {{index?: object}} [options] - The link index, for the package and
44
+ * vocabularies a written value is read against.
45
+ * @returns {object[]} Findings.
46
+ */
47
+ export function checkCultureSubType(note: object, { index }?: {
48
+ index?: object;
49
+ }): object[];
50
+ /**
51
+ * The `data.culture` field, declared once and spread into each type that takes
52
+ * it. A type gives its own `describe` where the field means something narrower
53
+ * there.
54
+ *
55
+ * @type {Readonly<object>}
56
+ */
57
+ export const CULTURE_FIELD: Readonly<object>;
@@ -5,7 +5,7 @@
5
5
  * @property {string} name - The key as an author writes it.
6
6
  * @property {string} [kind] - Its value's shape: `string`, `date`, `number`,
7
7
  * `integer`, `boolean`, `address`, `list`, `map`, `scalar-or-map`,
8
- * `list-or-map` or `string-or-list` — see {@link fitsKind}. Absent, the check
8
+ * or `string-or-list` — see {@link fitsKind}. Absent, the check
9
9
  * makes no claim about the value.
10
10
  * @property {readonly string[]} [allowed] - The closed set a present value is
11
11
  * one of, where the key takes a fixed word.
@@ -94,7 +94,7 @@ export type InnerKeySpec = {
94
94
  /**
95
95
  * - Its value's shape: `string`, `date`, `number`,
96
96
  * `integer`, `boolean`, `address`, `list`, `map`, `scalar-or-map`,
97
- * `list-or-map` or `string-or-list` — see {@link fitsKind}. Absent, the check
97
+ * or `string-or-list` — see {@link fitsKind}. Absent, the check
98
98
  * makes no claim about the value.
99
99
  */
100
100
  kind?: string | undefined;
@@ -20,8 +20,7 @@ export function derivedMessage(key: string, from: string, file?: string): string
20
20
  /**
21
21
  * The derived keys a note actually writes, in declaration order.
22
22
  *
23
- * **Presence is the whole test**, as it is for a retired or a runtime-only
24
- * field: an authored empty string is as much a claim on the key as prose is,
23
+ * **Presence is the whole test**, as it is for a runtime-only field: an authored empty string is as much a claim on the key as prose is,
25
24
  * and it is the same belief the message exists to correct. So the question is
26
25
  * whether the path resolves to anything at all, never whether the value is a
27
26
  * usable one.
@@ -114,10 +114,8 @@ export function positionInBody(body: string, offset: number, { bodyLine, bodyCol
114
114
  * The key match tolerates leading whitespace by default, so a nested key of the
115
115
  * same name answers when no top-level one is present — which is usually what a
116
116
  * reader wants, the key being unique in nearly every note. Pass `topLevel` where
117
- * it is not: `aliases` is both a retired top-level field and a **permitted**
118
- * `name.aliases`, and a finding about the first must never open on the
119
- * second, which would tell an author to delete a field they are allowed to
120
- * write.
117
+ * it is not: a finding about a top-level key must never open on a nested key
118
+ * of the same name.
121
119
  *
122
120
  * @param {string} raw - The file's full contents, frontmatter included.
123
121
  * @param {string} key - The top-level frontmatter key.
@@ -137,6 +135,31 @@ export function positionInFrontmatter(raw: string, key: string, value?: string,
137
135
  line?: number;
138
136
  column?: number;
139
137
  };
138
+ /**
139
+ * A frontmatter key's position in a note's file, or nothing.
140
+ *
141
+ * {@link positionInFrontmatter} answers the same question from the file's
142
+ * *text*; this reads the file to ask it.
143
+ *
144
+ * @param {string|undefined} absPath - The note's file.
145
+ * @param {string} key - The frontmatter key.
146
+ * @param {string} [value] - When given, prefer the occurrence whose line also
147
+ * carries this text — so a finding about one entry of a block opens on that
148
+ * entry rather than on the key that introduces it.
149
+ * @param {object} [options] - Options, forwarded to
150
+ * {@link positionInFrontmatter}.
151
+ * @param {boolean} [options.topLevel=false] - Require the key at column 1, so
152
+ * an identically named nested key cannot answer for it.
153
+ * @returns {{line?: number, column?: number}|undefined} Spreadable position
154
+ * fields, dropped rather than guessed when the file cannot be read or the key
155
+ * cannot be found — as `formatDiagnostic` requires.
156
+ */
157
+ export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string, { topLevel }?: {
158
+ topLevel?: boolean | undefined;
159
+ }): {
160
+ line?: number;
161
+ column?: number;
162
+ } | undefined;
140
163
  /**
141
164
  * Where a literal sits in a text, so a finding about it can be opened.
142
165
  *
@@ -53,12 +53,9 @@ export function defineDocumentSubtypes({ system, block, types }?: {
53
53
  /**
54
54
  * The row a system declares for a note type, or nothing.
55
55
  *
56
- * The **one** place a map is indexed, so the retirement window for a renamed
57
- * type is honoured everywhere a row is asked for — {@link documentSubtype},
58
- * {@link mapsNoteType}, {@link noteTypesFor}'s consumers and
59
- * {@link systemOf} all arrive here. A note still spelling `armorgear` finds the
60
- * `armor` row, compiles into the `armorgear` document it always did, and keeps
61
- * the address it publishes at; only the *report* tells it to move.
56
+ * The **one** place a map is indexed — {@link documentSubtype},
57
+ * {@link mapsNoteType}, {@link noteTypesFor}'s consumers and {@link systemOf}
58
+ * all arrive here.
62
59
  *
63
60
  * @param {DocumentSubtypeMap} map - The system's map.
64
61
  * @param {string|undefined} noteType - The note's declared `type`.
@@ -114,18 +111,6 @@ export function documentSubtype(map: DocumentSubtypeMap, noteType: string | unde
114
111
  file?: string | undefined;
115
112
  absPath?: string | undefined;
116
113
  }): string | undefined;
117
- /**
118
- * The answer {@link referencedSubtype} gives: a subtype, or why there is none.
119
- *
120
- * Exactly one of the two fields is set. A `problem` is a sentence a caller
121
- * prefixes with its own context and emits as a finding — it never throws,
122
- * because the caller resolving a reference is walking a list and has to report
123
- * this one and carry on.
124
- *
125
- * @typedef {object} ReferencedSubtype
126
- * @property {string} [subType] - The document subtype the reference addresses.
127
- * @property {string} [problem] - Why the reference names no document subtype.
128
- */
129
114
  /**
130
115
  * The document subtype a `(type, shortcode)` **reference** addresses.
131
116
  *
@@ -165,26 +150,22 @@ export function documentSubtype(map: DocumentSubtypeMap, noteType: string | unde
165
150
  * only the row has no block to read a discriminator from, so nothing here
166
151
  * can choose, and choosing anyway would be right about half the time.
167
152
  *
168
- * A **retired** spelling is refused by name before any of that. Without it a
169
- * reference left behind by a merge would take the unmapped fallback and address
170
- * a document of the old name — resolving silently, which is precisely what a
171
- * retirement exists to stop.
172
- *
173
- * A **renamed** spelling is the opposite case and resolves normally, through
174
- * the same {@link currentType} every other lookup goes through. It has to
175
- * be this side as much as the note's own `type:`: the overwhelming majority of
176
- * the 31,000 occurrences of the old names are `(type, shortcode)` references
177
- * inside a being's `items:` list, so a window that resolved notes but not
178
- * references would silently drop 30,000 embedded items rather than compile them
179
- * unchanged.
153
+ * A type that no row names, that no row produces as a subtype and that the
154
+ * consumer does not declare is refused: stamping the authored spelling on it
155
+ * would address a document that cannot exist.
180
156
  *
181
157
  * @param {DocumentSubtypeMap} map - The system's map.
182
158
  * @param {string|undefined} noteType - The type the reference names.
183
159
  * @param {string} document - The Foundry document class the reference must
184
160
  * address — `"Item"` for a being's embedded items.
161
+ * @param {object} [options] - Options.
162
+ * @param {ReadonlySet<string>} [options.declared] - The item types the
163
+ * consuming repository declares itself, which the map does not name.
185
164
  * @returns {ReferencedSubtype} The subtype, or why there is none.
186
165
  */
187
- export function referencedSubtype(map: DocumentSubtypeMap, noteType: string | undefined, document: string): ReferencedSubtype;
166
+ export function referencedSubtype(map: DocumentSubtypeMap, noteType: string | undefined, document: string, { declared }?: {
167
+ declared?: ReadonlySet<string> | undefined;
168
+ }): ReferencedSubtype;
188
169
  /**
189
170
  * Which system defines the document a note of this type compiles into.
190
171
  *
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Every form an event may name a note by: its Address in the `note` system,
3
+ * and in its own document's system where that differs.
4
+ *
5
+ * @param {object} record - The note's index record.
6
+ * @returns {string[]} The forms, the `note` one first.
7
+ */
8
+ export function selfForms(record: object): string[];
1
9
  /**
2
10
  * The views each note is given, by section.
3
11
  *
@@ -20,11 +28,14 @@ export function eventViewSections(db: object, records: Iterable<object>, fileOf:
20
28
  * index record, and the `query` it writes for a note — given the note's
21
29
  * Address in each form an event may name it by, and the shape of the corpus —
22
30
  * or `null` where the corpus states nothing the view could select. A view's
23
- * `own` query, where it has one, is what decides whether the view has rows.
31
+ * `own` query, where it has one, is what decides whether the view has rows;
32
+ * a view with `owners` answers that for many notes in one query, given each
33
+ * note's Address forms.
24
34
  *
25
35
  * @type {ReadonlyArray<Readonly<{slug: string, heading: string,
26
36
  * applies: (record: object) => boolean,
27
- * query: (self: string[], shape: CorpusShape) => {fence: string, own?: string}|null}>>}
37
+ * query: (self: string[], shape: CorpusShape) => {fence: string, own?: string}|null,
38
+ * owners?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string>>}>>}
28
39
  */
29
40
  export const EVENT_VIEWS: ReadonlyArray<Readonly<{
30
41
  slug: string;
@@ -34,6 +45,7 @@ export const EVENT_VIEWS: ReadonlyArray<Readonly<{
34
45
  fence: string;
35
46
  own?: string;
36
47
  } | null;
48
+ owners?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string>>;
37
49
  }>>;
38
50
  export type CorpusShape = {
39
51
  /**
@@ -2,8 +2,9 @@
2
2
  * Compile a Foundry Scene export into a pack document.
3
3
  *
4
4
  * The Scene passes through as exported. Only the note-derived Scene ID,
5
- * compendium metadata, `data.fixup` and pins marked `#anchor` change; nothing
6
- * else inside the Scene is read or checked.
5
+ * compendium metadata, the fixups — `data.fixup` and the map fields that
6
+ * declare a Scene property, such as `data.thumbnail` — and pins marked
7
+ * `#anchor` change; nothing else inside the Scene is read or checked.
7
8
  *
8
9
  * @param {object} fm - The map note's frontmatter.
9
10
  * @param {string} markdown - Its converted Markdown body.