@heroiclands/package-build 23.1.0 → 23.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/bin/content-commands.mjs +68 -149
  3. package/bin/package-build.mjs +56 -10
  4. package/ci/check-banners.mjs +42 -6
  5. package/ci/confirm-npm-version.mjs +135 -0
  6. package/ci/content-format-reference.mjs +14 -5
  7. package/config.mjs +1 -0
  8. package/container.mjs +185 -4
  9. package/content-config.mjs +333 -210
  10. package/deploy.mjs +116 -10
  11. package/docs/api.md +238 -246
  12. package/docs/authoring/assets.md +11 -5
  13. package/docs/authoring/frontmatter.md +1 -1
  14. package/docs/authoring/links-and-markup.md +87 -0
  15. package/docs/commands.md +138 -99
  16. package/docs/configuration.md +277 -125
  17. package/docs/content-format.md +2 -0
  18. package/docs/diagnostics.md +48 -13
  19. package/docs/guides/book.md +341 -21
  20. package/docs/guides/e2e.md +13 -2
  21. package/docs/guides/icons.md +14 -4
  22. package/docs/guides/module-project.md +18 -8
  23. package/docs/guides/release-and-deploy.md +54 -6
  24. package/docs/guides/site.md +5 -2
  25. package/docs/project-setup.md +30 -10
  26. package/docs/reference/format-details.md +373 -170
  27. package/docs/reference/note-types.md +128 -109
  28. package/docs/reference/theme-pages.md +31 -6
  29. package/e2e.mjs +140 -28
  30. package/engine/actor-compiler.mjs +22 -31
  31. package/engine/address-charset.mjs +4 -17
  32. package/engine/address-fields.mjs +1 -1
  33. package/engine/address-render.mjs +12 -1
  34. package/engine/address-values.mjs +6 -0
  35. package/engine/address.mjs +46 -33
  36. package/engine/art-fields.mjs +30 -5
  37. package/engine/art-slots.mjs +10 -1
  38. package/engine/asset-bindings.mjs +12 -1
  39. package/engine/base-compiler.mjs +34 -65
  40. package/engine/being-age.mjs +2 -7
  41. package/engine/being-measurements.mjs +12 -1
  42. package/engine/calendar-artifacts.mjs +6 -0
  43. package/engine/calendar-choice.mjs +12 -1
  44. package/engine/calendar-format.mjs +12 -1
  45. package/engine/calendar-human.mjs +12 -1
  46. package/engine/compile-corpus.mjs +3 -1
  47. package/engine/content-address.mjs +9 -12
  48. package/engine/content-alerts.mjs +13 -1
  49. package/engine/content-blocks.mjs +14 -3
  50. package/engine/content-embeds.mjs +11 -3
  51. package/engine/content-figures.mjs +9 -0
  52. package/engine/content-footnotes.mjs +12 -1
  53. package/engine/content-format-check.mjs +11 -262
  54. package/engine/content-format.yaml +5 -2
  55. package/engine/content-images.mjs +3 -0
  56. package/engine/content-index.mjs +443 -20
  57. package/engine/content-links.mjs +29 -35
  58. package/engine/content-lint.mjs +2 -3
  59. package/engine/content-poetry.mjs +12 -1
  60. package/engine/content-slug.mjs +2 -14
  61. package/engine/content-spans.mjs +13 -1
  62. package/engine/content-tables.mjs +12 -1
  63. package/engine/culture-choice.mjs +210 -13
  64. package/engine/data-keys.mjs +19 -16
  65. package/engine/date-conversion.mjs +12 -1
  66. package/engine/declaration-check.mjs +9 -0
  67. package/engine/derived-fields.mjs +6 -7
  68. package/engine/diagnostics.mjs +34 -4
  69. package/engine/docs-checks.mjs +9 -0
  70. package/engine/document-subtypes.mjs +47 -34
  71. package/engine/event-views.mjs +139 -14
  72. package/engine/exported-scene.mjs +92 -28
  73. package/engine/extension-attributes.mjs +12 -1
  74. package/engine/field-spec.mjs +11 -102
  75. package/engine/folder-notes.mjs +0 -64
  76. package/engine/foreign-catalog.mjs +2 -1
  77. package/engine/format-generated.mjs +9 -0
  78. package/engine/foundry-entries.mjs +10 -28
  79. package/engine/frontmatter-lint.mjs +83 -392
  80. package/engine/frontmatter.mjs +20 -70
  81. package/engine/generate.mjs +10 -15
  82. package/engine/heading-attributes.mjs +12 -1
  83. package/engine/helpers.mjs +41 -102
  84. package/engine/holdings.mjs +1 -1
  85. package/engine/homepage.mjs +3 -10
  86. package/engine/ids.mjs +1 -167
  87. package/engine/index-records.mjs +6 -4
  88. package/engine/index.mjs +0 -3
  89. package/engine/infobox-render.mjs +15 -3
  90. package/engine/infobox.mjs +13 -14
  91. package/engine/item-compiler.mjs +4 -10
  92. package/engine/item-docs.mjs +3 -5
  93. package/engine/item-registry.mjs +2 -10
  94. package/engine/journals.mjs +1 -5
  95. package/engine/literature-notes.mjs +4 -12
  96. package/engine/macros.mjs +1 -2
  97. package/engine/map-graphviz.mjs +12 -1
  98. package/engine/markdown-expressions.mjs +12 -1
  99. package/engine/markdownlint-run.mjs +567 -0
  100. package/engine/metadata-index.mjs +42 -12
  101. package/engine/note-addresses.mjs +143 -35
  102. package/engine/note-claims.mjs +14 -28
  103. package/engine/note-dates.mjs +3 -3
  104. package/engine/note-frontmatter.mjs +6 -0
  105. package/engine/note-ids.mjs +8 -14
  106. package/engine/note-package.mjs +8 -25
  107. package/engine/note-state.mjs +1 -3
  108. package/engine/note-vocabulary.mjs +148 -84
  109. package/engine/number-words.mjs +12 -1
  110. package/engine/pack-config.mjs +15 -25
  111. package/engine/pack-router.mjs +10 -16
  112. package/engine/pathnames.mjs +8 -22
  113. package/engine/pdf-build.mjs +773 -121
  114. package/engine/pdf-coverage.mjs +188 -0
  115. package/engine/pdf-index.mjs +54 -7
  116. package/engine/pdf-pages.mjs +247 -0
  117. package/engine/pdf-render.mjs +114 -4
  118. package/engine/pdf-toc.mjs +29 -32
  119. package/engine/project-init.mjs +46 -6
  120. package/engine/prose-config.mjs +1 -1
  121. package/engine/prose-lint.mjs +52 -88
  122. package/engine/readability-lint.mjs +9 -0
  123. package/engine/reckoning-markers.mjs +12 -1
  124. package/engine/reference-targets.mjs +7 -0
  125. package/engine/runtime-only-fields.mjs +5 -8
  126. package/engine/scene-levels.mjs +2 -2
  127. package/engine/scenes.mjs +1 -1
  128. package/engine/shortcode-references.mjs +7 -0
  129. package/engine/site-build.mjs +30 -16
  130. package/engine/site-index.mjs +10 -16
  131. package/engine/site-preview.mjs +6 -0
  132. package/engine/site-root.mjs +8 -1
  133. package/engine/social-tie-terms.mjs +12 -1
  134. package/engine/social-ties.mjs +3 -0
  135. package/engine/sql-tables.mjs +56 -20
  136. package/engine/standing-terms.mjs +11 -1
  137. package/engine/standings.mjs +11 -27
  138. package/engine/system-block.mjs +39 -141
  139. package/engine/system-vocabulary.mjs +6 -0
  140. package/engine/walk-files.mjs +69 -0
  141. package/engine/web-wikilinks.mjs +23 -3
  142. package/engine/wikilink-syntax.mjs +32 -21
  143. package/engine/wikilinks.mjs +27 -16
  144. package/engine/yaml-lint.mjs +7 -4
  145. package/githooks/attribution.mjs +8 -0
  146. package/githooks/commit-message.mjs +9 -0
  147. package/hm3/actors.mjs +9 -48
  148. package/hm3/default-item-art.mjs +9 -9
  149. package/hm3/item-fields.mjs +2 -9
  150. package/hm3/template-priority.mjs +6 -7
  151. package/hugo-theme/data/banner-kinds.yaml +78 -0
  152. package/hugo-theme/data/banners.yaml +2 -3
  153. package/hugo-theme/layouts/404.html +3 -3
  154. package/hugo-theme/layouts/partials/banner-url.html +31 -7
  155. package/hugo-theme/layouts/partials/hero-banner.html +13 -14
  156. package/hugo-theme/layouts/partials/related.html +1 -1
  157. package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
  158. package/hugo-theme/static/css/style.css +1 -1
  159. package/manifest.mjs +11 -4
  160. package/markdownlint-config.mjs +1 -1
  161. package/package.json +6 -3
  162. package/release.mjs +83 -18
  163. package/sohl/actors.mjs +16 -27
  164. package/sohl/default-item-art.mjs +3 -3
  165. package/sohl/document-subtypes.mjs +1 -16
  166. package/sohl/infobox.mjs +2 -3
  167. package/sohl/item-builders.mjs +5 -8
  168. package/sohl/item-fields.mjs +5 -35
  169. package/sohl/items.mjs +0 -4
  170. package/sohl/kb-passes.mjs +3 -3
  171. package/types/container.d.mts +116 -5
  172. package/types/content-config.d.mts +83 -35
  173. package/types/deploy.d.mts +42 -6
  174. package/types/e2e.d.mts +35 -3
  175. package/types/engine/address-charset.d.mts +4 -17
  176. package/types/engine/address.d.mts +17 -8
  177. package/types/engine/art-fields.d.mts +7 -4
  178. package/types/engine/art-slots.d.mts +6 -0
  179. package/types/engine/base-compiler.d.mts +15 -17
  180. package/types/engine/content-address.d.mts +9 -12
  181. package/types/engine/content-embeds.d.mts +4 -2
  182. package/types/engine/content-format-check.d.mts +0 -64
  183. package/types/engine/content-index.d.mts +82 -3
  184. package/types/engine/content-links.d.mts +2 -4
  185. package/types/engine/culture-choice.d.mts +51 -8
  186. package/types/engine/data-keys.d.mts +2 -2
  187. package/types/engine/derived-fields.d.mts +1 -2
  188. package/types/engine/diagnostics.d.mts +27 -4
  189. package/types/engine/document-subtypes.d.mts +12 -31
  190. package/types/engine/event-views.d.mts +14 -2
  191. package/types/engine/exported-scene.d.mts +3 -2
  192. package/types/engine/field-spec.d.mts +13 -122
  193. package/types/engine/folder-notes.d.mts +0 -39
  194. package/types/engine/foreign-catalog.d.mts +2 -1
  195. package/types/engine/foundry-entries.d.mts +4 -5
  196. package/types/engine/frontmatter-lint.d.mts +2 -2
  197. package/types/engine/frontmatter.d.mts +19 -45
  198. package/types/engine/generate.d.mts +1 -1
  199. package/types/engine/helpers.d.mts +25 -42
  200. package/types/engine/homepage.d.mts +3 -10
  201. package/types/engine/ids.d.mts +0 -114
  202. package/types/engine/index-records.d.mts +6 -4
  203. package/types/engine/index.d.mts +0 -1
  204. package/types/engine/infobox-render.d.mts +7 -1
  205. package/types/engine/infobox.d.mts +3 -2
  206. package/types/engine/item-docs.d.mts +1 -1
  207. package/types/engine/item-registry.d.mts +2 -4
  208. package/types/engine/literature-notes.d.mts +1 -1
  209. package/types/engine/markdownlint-run.d.mts +40 -0
  210. package/types/engine/metadata-index.d.mts +9 -1
  211. package/types/engine/note-addresses.d.mts +27 -2
  212. package/types/engine/note-claims.d.mts +3 -5
  213. package/types/engine/note-ids.d.mts +2 -4
  214. package/types/engine/note-package.d.mts +1 -1
  215. package/types/engine/note-state.d.mts +1 -3
  216. package/types/engine/note-vocabulary.d.mts +28 -21
  217. package/types/engine/pack-config.d.mts +2 -6
  218. package/types/engine/pack-router.d.mts +4 -4
  219. package/types/engine/pathnames.d.mts +3 -3
  220. package/types/engine/pdf-build.d.mts +98 -12
  221. package/types/engine/pdf-coverage.d.mts +60 -0
  222. package/types/engine/pdf-index.d.mts +34 -5
  223. package/types/engine/pdf-pages.d.mts +143 -0
  224. package/types/engine/pdf-render.d.mts +45 -1
  225. package/types/engine/pdf-toc.d.mts +12 -14
  226. package/types/engine/prose-config.d.mts +1 -1
  227. package/types/engine/prose-lint.d.mts +11 -10
  228. package/types/engine/runtime-only-fields.d.mts +2 -2
  229. package/types/engine/site-build.d.mts +9 -5
  230. package/types/engine/site-index.d.mts +4 -5
  231. package/types/engine/site-root.d.mts +1 -1
  232. package/types/engine/sql-tables.d.mts +31 -2
  233. package/types/engine/standing-terms.d.mts +2 -1
  234. package/types/engine/standings.d.mts +7 -9
  235. package/types/engine/system-block.d.mts +10 -44
  236. package/types/engine/walk-files.d.mts +15 -0
  237. package/types/engine/wikilink-syntax.d.mts +18 -19
  238. package/types/engine/yaml-lint.d.mts +5 -4
  239. package/types/hm3/actors.d.mts +1 -2
  240. package/types/hm3/item-fields.d.mts +2 -4
  241. package/types/hm3/template-priority.d.mts +6 -7
  242. package/types/manifest.d.mts +3 -2
  243. package/types/release.d.mts +12 -7
  244. package/types/sohl/actors.d.mts +2 -1
  245. package/types/sohl/document-subtypes.d.mts +1 -2
  246. package/types/sohl/item-fields.d.mts +3 -7
  247. package/engine/retired-fields.mjs +0 -631
  248. package/types/engine/retired-fields.d.mts +0 -354
@@ -74,10 +74,16 @@ export function collectHomepages(contentBase: string, ctx: object): {
74
74
  * @param {import("./related-pages.mjs").Related} [options.related] - The
75
75
  * homepage's backlinks and mentions. Absent where nothing connects to it,
76
76
  * and when the tree contains only a homepage, where no content link resolves.
77
+ * @param {readonly object[]} [options.records] - The corpus, so a `data.banner`
78
+ * address resolves against the images this package ships.
79
+ * @param {object} [options.foreign] - The dependencies' vendored indexes, so a
80
+ * `data.banner` may name an image a dependency ships.
77
81
  * @returns {number} How many pages were written.
78
82
  */
79
- export function writeHomepages(outRoot: string, pages: readonly object[], config: object, { related }?: {
83
+ export function writeHomepages(outRoot: string, pages: readonly object[], config: object, { related, records, foreign }?: {
80
84
  related?: import("./related-pages.mjs").Related | undefined;
85
+ records?: readonly object[] | undefined;
86
+ foreign?: object | undefined;
81
87
  }): number;
82
88
  /**
83
89
  * The integrity gates a site build runs before it writes anything.
@@ -147,9 +153,7 @@ export function tableUniverse(pages: object[]): Map<string, object[]>;
147
153
  /**
148
154
  * The frontmatter a page publishes with.
149
155
  *
150
- * An authored `aliases` is retired and refused before a build reaches
151
- * here, which makes this a guard rather than a working path. It is a list of
152
- * names a reader might call the note. Hugo reads
156
+ * An authored `aliases` is a list of names a reader might call the note. Hugo reads
153
157
  * `aliases` as **URL redirects**, so passing them through would publish a
154
158
  * redirect stub at each name. They are dropped, and this build emits no
155
159
  * redirects of its own.
@@ -171,7 +175,7 @@ export function tableUniverse(pages: object[]): Map<string, object[]>;
171
175
  * `<base><slug>/`.
172
176
  *
173
177
  * A content page carries the package the build **derived**. No note
174
- * declares one — `package:` is retired — so the note's frontmatter alone
178
+ * declares one, so the note's frontmatter alone
175
179
  * would publish a page that does not say which package it belongs to. The
176
180
  * emitted page is what a theme reads: `breadcrumbs.html` reads
177
181
  * `.Params.package`. Writing the derived value keeps a page
@@ -104,11 +104,10 @@ export function resolveCrossNoteFigures(target: string, { contentTypes, packages
104
104
  * the one pass that resolves them.
105
105
  * @returns {object} The resolver context.
106
106
  *
107
- * There is deliberately **no `manifestsComplete`**. It used to let a resolver
108
- * soften an unresolved cross-package address while any package's manifest was
109
- * missing; the softening is retired, since the pack compilers and the link
110
- * checker never had it and one authored link must not get two verdicts. A
111
- * caller still passing it is ignored rather than obeyed.
107
+ * There is deliberately **no `manifestsComplete`**: an unresolved cross-package
108
+ * address is never softened while a manifest is missing, since the pack
109
+ * compilers and the link checker do not soften it and one authored link must
110
+ * not get two verdicts.
112
111
  */
113
112
  export function wikiContext(built: SiteIndex, { src, file, type, errors, foreignIndex, assets, resolved }: {
114
113
  src: string;
@@ -42,7 +42,7 @@ export function indexSite(where: {
42
42
  * sits there. Left in place it would redirect the prefix root — the homepage —
43
43
  * to an address nothing publishes. The same holds for a `pagefind/` left by an
44
44
  * earlier build when `search` is off: deployed, it is a working search over
45
- * pages that may no longer exist, so it goes too.
45
+ * pages that may not exist, so it goes too.
46
46
  *
47
47
  * `_headers` is written before the index is built, so a failed index leaves
48
48
  * the root files as they would be — the failure is the indexer's, reported as
@@ -98,7 +98,7 @@ export function runSqlQuery(db: object, sql: string): Promise<{
98
98
  * ships a link that does not resolve. A `_section` alias splits the result into
99
99
  * a headed table per distinct value, in the order the rows arrive — so the
100
100
  * authored `ORDER BY` decides the section order too, and one query replaces the
101
- * forty near-identical blocks the language used to require.
101
+ * forty near-identical blocks it would otherwise take.
102
102
  *
103
103
  * **A result selecting nothing still renders its header and rule.** The finding
104
104
  * is the point, not withholding the output: an authored heading with an empty
@@ -212,6 +212,8 @@ export function generatedSectionsFor(db: object | undefined, records: object[],
212
212
  * From {@link generatedSectionsFor}.
213
213
  * @param {Array<{source: string, markdown: string, frontmatter?: object}>} sources -
214
214
  * The bodies to answer, extended in place.
215
+ * @param {Map<string, {body: string, frontmatter: object|null}>} [parsed] -
216
+ * Notes already parsed, keyed by file; a note absent from it is read.
215
217
  * @returns {void}
216
218
  */
217
219
  export function joinGeneratedSections(generated: {
@@ -221,6 +223,9 @@ export function joinGeneratedSections(generated: {
221
223
  source: string;
222
224
  markdown: string;
223
225
  frontmatter?: object;
226
+ }>, parsed?: Map<string, {
227
+ body: string;
228
+ frontmatter: object | null;
224
229
  }>): void;
225
230
  /**
226
231
  * Record each note's generated sections on its prepared results, where the
@@ -235,6 +240,17 @@ export function joinGeneratedSections(generated: {
235
240
  export function attachGeneratedSections(prepared: Map<string, object[]>, generated: {
236
241
  sections: Map<string, string>;
237
242
  }): void;
243
+ /**
244
+ * Each declared dependency's cached index, as {@link openNotesDatabase} attaches
245
+ * them. Empty where none has been fetched.
246
+ *
247
+ * @param {object} [config] - Resolved configuration, defaulting to ambient.
248
+ * @returns {Promise<Array<{id: string, file: string}>>} The dependencies.
249
+ */
250
+ export function cachedDependencies(config?: object): Promise<Array<{
251
+ id: string;
252
+ file: string;
253
+ }>>;
238
254
  /**
239
255
  * Answer every `sql` directive in a content tree.
240
256
  *
@@ -259,6 +275,14 @@ export function attachGeneratedSections(prepared: Map<string, object[]>, generat
259
275
  * @param {object[]} [opts.records] - Index records the caller already derived.
260
276
  * A command that also builds a link index holds them already, and deriving
261
277
  * them twice is the duplicated-corpus failure this closes.
278
+ * @param {Map<string, {body: string, frontmatter: object|null}>} [opts.parsed] -
279
+ * The notes {@link module:engine/content-index.indexRecordsFor} parsed for
280
+ * `records`, keyed by file, so no note is parsed twice. A note absent from it
281
+ * is read.
282
+ * @param {object} [opts.database] - An open notes database over `records`, from
283
+ * {@link openNotesDatabase} with this call's `audience` and the declared
284
+ * dependencies from {@link cachedDependencies}. It is queried and left open;
285
+ * absent, one is opened and closed here.
262
286
  * @param {"all"|"public"} [opts.audience] - Whether to exclude GM notes.
263
287
  * @param {Map<string, object>} [opts.foreignIndex] - The fetched indexes the
264
288
  * generated sections take in, where the caller holds them already; absent,
@@ -267,10 +291,15 @@ export function attachGeneratedSections(prepared: Map<string, object[]>, generat
267
291
  * carrying `inline` and `pageLists` beside them, or nothing when the tree
268
292
  * holds no such directive and no note is given a generated section.
269
293
  */
270
- export function prepareTreeSqlTables(contentBase: string, { config, skipDirectories, records, audience, foreignIndex }?: {
294
+ export function prepareTreeSqlTables(contentBase: string, { config, skipDirectories, records, parsed, database, audience, foreignIndex }?: {
271
295
  config?: object | undefined;
272
296
  skipDirectories?: readonly string[] | undefined;
273
297
  records?: object[] | undefined;
298
+ parsed?: Map<string, {
299
+ body: string;
300
+ frontmatter: object | null;
301
+ }> | undefined;
302
+ database?: object | undefined;
274
303
  audience?: "all" | "public" | undefined;
275
304
  foreignIndex?: Map<string, object> | undefined;
276
305
  }): Promise<Map<string, object[]> | undefined>;
@@ -21,7 +21,8 @@ export const STANDING_BODY_TYPES: readonly string[];
21
21
  *
22
22
  * `rank` is the level on the body's own `governance.ranks` ladder, and every
23
23
  * membership states one. `office` is a key of its `governance.offices` map, and
24
- * is stated only where the being holds a post.
24
+ * is stated only where the being holds a post. `societies` names the societies
25
+ * the membership is held in, as a list because a membership can span several.
25
26
  *
26
27
  * @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
27
28
  */
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * The standings a being authored, in one shape whichever shape it wrote.
3
3
  *
4
- * The list form names bodies and no standings; the map form names both. Three
5
- * spellings reach here and every reader downstream takes the same entries, so
6
- * none of them asks which it was handed:
4
+ * Two spellings reach here and every reader downstream takes the same entries,
5
+ * so none of them asks which it was handed:
7
6
  *
8
7
  * - an **{@link AddressEntries}** — the map after the note boundary decoded its
9
8
  * keys into typed Addresses, which is the shape every compile sees;
10
- * - a **plain map**, which is the shape a checker reading raw YAML sees;
11
- * - a **list**, which names bodies and says nothing about standing in them.
9
+ * - a **plain map**, which is the shape a checker reading raw YAML sees.
12
10
  *
13
11
  * An emptied map arrives from the property editor as `[]` and means what `{}`
14
12
  * means — this being authors no membership — so both read as no entries.
@@ -18,12 +16,12 @@
18
16
  * Address is what a finding should name.
19
17
  *
20
18
  * @param {unknown} value - The authored `data.affiliations`.
21
- * @returns {{form: "absent"|"list"|"map"|"malformed",
19
+ * @returns {{form: "absent"|"map"|"malformed",
22
20
  * entries: Array<{body: unknown, sourceKey: string|number, standing: unknown}>}}
23
21
  * The form it was written in, and its entries.
24
22
  */
25
23
  export function readStandings(value: unknown): {
26
- form: "absent" | "list" | "map" | "malformed";
24
+ form: "absent" | "map" | "malformed";
27
25
  entries: Array<{
28
26
  body: unknown;
29
27
  sourceKey: string | number;
@@ -128,7 +126,7 @@ export function standingLead(standing: object, digest?: {
128
126
  * Five questions, and the last three are what keying the entry by its body
129
127
  * buys:
130
128
  *
131
- * 1. the value is a map keyed by Address (or the list this window still takes);
129
+ * 1. the value is a map keyed by Address;
132
130
  * 2. every key names an affiliation that resolves, once;
133
131
  * 3. the entry states a standing at all — what a stated standing holds is the
134
132
  * inner-key check's, read from {@link STANDING_FIELDS};
@@ -136,7 +134,7 @@ export function standingLead(standing: object, digest?: {
136
134
  * 5. an `office` is a key of that body's `governance.offices`.
137
135
  *
138
136
  * Both of the last two are read from the named body's own frontmatter as the
139
- * lint runs, so a renamed rung or a renamed post fails on every being pointing
137
+ * lint runs, so a rung or a post that does not exist fails on every being pointing
140
138
  * at it rather than on a list somebody has to remember to edit.
141
139
  *
142
140
  * @param {object} note - The note.
@@ -95,17 +95,14 @@ export function sharedProperty(fm: object, source: string, defaultValue?: any):
95
95
  */
96
96
  export function blockProperty(fm: object, block: string, key: string, defaultValue?: any): any;
97
97
  /**
98
- * A system override followed by a shared `data:` value followed by the
99
- * retiring top-level spelling `data:` gathered it off, reporting which one
98
+ * A system override followed by a shared `data:` value, reporting which one
100
99
  * answered.
101
100
  *
102
101
  * {@link module:engine/field-spec.resolveFieldValue}'s counterpart for a
103
102
  * universal `data:` key — `pack` and `packFolder`, declared for every note
104
103
  * type rather than per type, so neither is a `FieldSpec` that resolver takes.
105
104
  * The order is the same shape: a system's own override, then the shared
106
- * `data:` position, then the bare top-level key a tree authored before
107
- * `data:` existed. The top-level value stays readable while a tree still on
108
- * it moves its authored fields.
105
+ * `data:` position.
109
106
  *
110
107
  * @param {object} fm - The note's frontmatter.
111
108
  * @param {string} block - The system block key.
@@ -132,46 +129,15 @@ export function blockDataProperty(fm: object, block: string, key: string, defaul
132
129
  /**
133
130
  * The key a field is authored at **inside** a system block — step 2.
134
131
  *
135
- * `legacyKey` when the field declares one, and `name` otherwise. The fallback
136
- * is what makes this change invisible to every declaration written before it:
137
- * a field whose shared source and in-block key are the same word says so once,
138
- * as it always did.
139
- *
140
- * Named and exported rather than spelled inline because three readers ask the
141
- * question and must agree — the resolver here, the frontmatter lint building
142
- * the set of keys a block may carry, and the author-facing surfaces naming
143
- * where a value was written.
144
- *
145
- * @param {{name?: string, legacyKey?: string}} field - The declaration.
146
- * @returns {string|undefined} The in-block key, or `undefined` for a field that
147
- * is not authored at all.
148
- */
149
- export function legacyKeyOf(field: {
150
- name?: string;
151
- legacyKey?: string;
152
- }): string | undefined;
153
- /**
154
- * The bare top-level key a `data:`-sourced field is being swept off — step 3b.
155
- *
156
- * The retiring spelling of `data.portrait` is not a second declaration anyone
157
- * has to write — it is `portrait`, mechanically, and the same holds for every
158
- * other key the container gathered. Deriving it is what keeps the two spellings
159
- * of one field from disagreeing the way two declarations would.
160
- *
161
- * **Only a `data.` source has one.** `protection.blunt` and `impact.die` are
162
- * paths into containers a note has always written at the top level; they were
163
- * never `blunt:` or `die:`, and reading those would invent a position rather
164
- * than remember one.
165
- *
166
- * A field declaring {@link module:engine/field-spec.FieldSpec `topLevelMeans`}
167
- * has no shared position at all, retiring or otherwise — the resolver checks
168
- * that before asking.
132
+ * The field's `name`, except that a name which is a path into `data:` has no
133
+ * in-block position. Named and exported because the resolver and the
134
+ * frontmatter lint, which builds the set of keys a block may carry, must agree.
169
135
  *
170
136
  * @param {{name?: string}} field - The declaration.
171
- * @returns {string|undefined} The retiring top-level path, or `undefined` for a
172
- * field whose shared source never lived there.
137
+ * @returns {string|undefined} The in-block key, or `undefined` for a field that
138
+ * is not authored in the block.
173
139
  */
174
- export function retiredTopLevelKey(field: {
140
+ export function inBlockKey(field: {
175
141
  name?: string;
176
142
  }): string | undefined;
177
143
  /**
@@ -198,7 +164,7 @@ export function authoredKey(name: string | undefined): string;
198
164
  * can distinguish a value an author wrote from one a default supplied, which
199
165
  * the value alone never says.
200
166
  *
201
- * @typedef {"system"|"block"|"shared"|"topLevel"|"default"|"value"} FieldSource
167
+ * @typedef {"system"|"block"|"shared"|"default"|"value"} FieldSource
202
168
  */
203
169
  /**
204
170
  * Resolve one declared field against a note, in the declared order.
@@ -394,4 +360,4 @@ export const SYSTEM_BLOCK_KEYS: ReadonlySet<string>;
394
360
  * can distinguish a value an author wrote from one a default supplied, which
395
361
  * the value alone never says.
396
362
  */
397
- export type FieldSource = "system" | "block" | "shared" | "topLevel" | "default" | "value";
363
+ export type FieldSource = "system" | "block" | "shared" | "default" | "value";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Every file under a root, minus the directories nothing should walk.
3
+ *
4
+ * @param {string} root - Absolute path to walk.
5
+ * @param {object} [hooks]
6
+ * @param {(dir: string) => void} [hooks.enter] - Called with each directory
7
+ * before its entries are read.
8
+ * @param {(abs: string, isDirectory: boolean) => boolean} [hooks.skip] -
9
+ * Whether to leave a path out; a skipped directory is not entered.
10
+ * @returns {string[]} Absolute file paths, in directory order.
11
+ */
12
+ export function walkFiles(root: string, hooks?: {
13
+ enter?: ((dir: string) => void) | undefined;
14
+ skip?: ((abs: string, isDirectory: boolean) => boolean) | undefined;
15
+ }): string[];
@@ -56,15 +56,10 @@ export function authoredLabel({ display }: {
56
56
  * the form to write rather than a value to correct: there is no value that
57
57
  * makes an unlabelled link resolve.
58
58
  *
59
- * **Why there is nothing left for a bare link to mean**. The pipe used
60
- * to select between two namespaces — address and alias — and the alias one was
61
- * empty in practice: across 8,305 wikilinks in three content trees, not one
62
- * bare `[[Alias]]` resolved to a note. What the index it looked up in *did* do
63
- * was fold every note's `name.full` into itself, which forbade two notes of a
64
- * type from sharing a display name. So the namespace is gone, every
65
- * link is an address, and an address needs the pipe that says so.
66
- *
67
- * The **link part may still be an anchor**: `[[#slug|Text]]` addresses a
59
+ * **A bare link has nothing to mean.** Every link is an address, and an
60
+ * address needs the pipe that says so.
61
+ *
62
+ * The **link part may be an anchor**: `[[#slug|Text]]` addresses a
68
63
  * section of the page it is written on. It is the label that is required, not
69
64
  * a target.
70
65
  *
@@ -100,13 +95,9 @@ export function isEventAnchor(hit: {
100
95
  * package publishing it is not a declared dependency — or is one whose index
101
96
  * has not been fetched — and the link itself looks identical in every case.
102
97
  *
103
- * As a **warning** in the checker and, in the site build, nothing
104
- * at all until every linkable package's manifest was accounted for — on the
105
- * reasoning that a bare `[[Name]]` might be a placeholder for a note nobody had
106
- * written yet. That reasoning was a property of the bare form, which is retired;
107
- * the intent behind it now has a real spelling, a `draft`-tagged note
108
- * that exists and resolves and renders marked. So an address naming no
109
- * note is a typo or an omission, both want fixing, and all three builds say so.
98
+ * An address naming no note is a typo or an omission: the intent of a
99
+ * placeholder for a note nobody has written yet is spelled as a `draft`-tagged
100
+ * note that exists, resolves and renders marked. Both want fixing, and all three builds say so.
110
101
  *
111
102
  * @param {string} target - The address as authored, named in the message.
112
103
  * @returns {string} The message, unpunctuated at the end as a finding is.
@@ -165,17 +156,23 @@ export function ambiguousAddressMessage(target: string, packages?: Iterable<stri
165
156
  * @param {string} [finding.type] - For `not-an-asset`, the type the address
166
157
  * named.
167
158
  * @param {string} [finding.stub] - For `stub`, where the stub is.
159
+ * @param {string} [finding.shortcode] - For `shortcode-not-lowercase`, the
160
+ * shortcode as written.
161
+ * @param {string} [finding.declared] - For `shortcode-not-lowercase`, the
162
+ * lowercase spelling, when it is a declared shortcode.
168
163
  * @returns {string} The message.
169
164
  * @throws {Error} On a reason outside the closed set — a resolver inventing one
170
165
  * would otherwise report a link with no explanation at all.
171
166
  */
172
- export function linkFindingMessage({ reason, target, packages, anchor, type, stub }: {
167
+ export function linkFindingMessage({ reason, target, packages, anchor, type, stub, shortcode, declared, }: {
173
168
  reason: string;
174
169
  target: string;
175
170
  packages?: Iterable<string> | undefined;
176
171
  anchor?: string | undefined;
177
172
  type?: string | undefined;
178
173
  stub?: string | undefined;
174
+ shortcode?: string | undefined;
175
+ declared?: string | undefined;
179
176
  }): string;
180
177
  /**
181
178
  * Whether a parsed link addresses a section of the page it is written on.
@@ -221,8 +218,8 @@ export function readWikilink(inner: string, context: object): object;
221
218
  *
222
219
  * The one rule about a link's *shape* that both resolvers share also lives here
223
220
  * — **a link carries a label** ({@link unlabelledLinkMessage}) — because the
224
- * two used to state it in their own words and the author met whichever ran
225
- * first.
221
+ * two state it through one message and the author reads the same words
222
+ * whichever ran first.
226
223
  *
227
224
  * For the same reason, so does the **vocabulary of link findings**
228
225
  * ({@link LINK_FINDING_REASONS}) and the message each one reports through
@@ -261,6 +258,8 @@ export const WIKILINK: RegExp;
261
258
  * - `unlabelled` — no `|`, so the link addresses nothing.
262
259
  * - `not-an-address` — labelled, but the target does not parse as an address.
263
260
  * - `not-lowercase` — a package, system or type segment carries a capital.
261
+ * - `shortcode-not-lowercase` — the shortcode segment carries a capital. A
262
+ * shortcode is lowercase, and the link is refused rather than lowercased.
264
263
  * - `unknown-type` — definitely qualified, but names no type this build knows.
265
264
  * - `not-an-asset` — a real address, embedded where only a file can be drawn.
266
265
  * - `unresolved` — parses as an address, and nothing publishes it.
@@ -22,10 +22,11 @@
22
22
  * - `no-empty-key`, `no-empty-document` — a fence or a file that parses to
23
23
  * nothing at all.
24
24
  *
25
- * **GitHub workflows are exempt from `no-empty-mapping-value`**, because an
26
- * empty value is the language there: `on:` `push:` and `workflow_dispatch:`
27
- * carry their meaning by being present, and writing `push: null` to satisfy a
28
- * linter would be worse YAML, not better.
25
+ * **Workflows are exempt from `no-empty-mapping-value`**, under
26
+ * `.github/workflows/` and Gitea's `.gitea/workflows/` alike, because an empty
27
+ * value is the language there: `on:` `push:` and `workflow_dispatch:` carry
28
+ * their meaning by being present, and writing `push: null` to satisfy a linter
29
+ * would be worse YAML, not better.
29
30
  *
30
31
  * @param {object} plugin - The `eslint-plugin-yml` module.
31
32
  * @returns {object[]} A complete flat config, for `overrideConfig`.
@@ -10,8 +10,7 @@ export const ACTOR_FIELDS: readonly import("../engine/field-spec.mjs").FieldSpec
10
10
  /**
11
11
  * The two `data:` facts HM3 declares on a `character` and not on a `creature`.
12
12
  *
13
- * Both name their shared source and their legacy in-block key, for the reason
14
- * {@link ACTOR_FIELDS} does.
13
+ * Both name a shared source in `data:`, which is the only place they are authored.
15
14
  *
16
15
  * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
17
16
  */
@@ -7,10 +7,8 @@
7
7
  * a registry is addressed by what a note calls itself. What the document is
8
8
  * called is the map's answer, and only the map's.
9
9
  *
10
- * Each field names both of its authored positions, as
11
- * {@link module:hm3/actors} does and for the same reason: `name` is the shared
12
- * source the content format's mapping tables state, and `legacyKey` is the key
13
- * the `hm3:` block still carries.
10
+ * A field whose `name` is a path into `data:` is authored there and nowhere
11
+ * else: `name` is the shared source the content format's mapping tables state.
14
12
  *
15
13
  * @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
16
14
  */
@@ -6,13 +6,12 @@
6
6
  * than a `null` nothing reads.
7
7
  *
8
8
  * **Read through the shared resolver, not a field declaration**. A
9
- * `FieldSpec`'s shared source is a single position, and this value has five:
10
- * `data:`, this block, the top level, and the retiring `archetype` spelling in
11
- * the latter two. The resolver is the single implementation of what a note said,
12
- * so the two systems cannot disagree about it. It is read against **this**
13
- * block: the `sohl:` block is not a source for an HM3 document, so a tree that
14
- * still states the priority there (`harn-ensemble`, on 2,502 notes) writes no
15
- * HM3 flag until it sweeps to `data:`.
9
+ * `FieldSpec`'s shared source is a single position, and this value has three:
10
+ * `data:`, this block, and the top level. The resolver is the single
11
+ * implementation of what a note said, so the two systems cannot disagree about
12
+ * it. It is read against **this** block: the `sohl:` block is not a source for
13
+ * an HM3 document, so a note that states the priority only there writes no HM3
14
+ * flag.
16
15
  *
17
16
  * @param {object} fm - The note's frontmatter.
18
17
  * @param {string} block - This pass's system block.
@@ -190,8 +190,9 @@ export function publishedRelationships(relationships: Record<string, unknown>):
190
190
  * a key Foundry adds later needs no release of this package. The one
191
191
  * exception is `descriptionHtml`, folded into the description below rather
192
192
  * than surviving under its own name.
193
- * - **Derived** — the identity, the description, the release addresses, the
194
- * version, the Foundry and system compatibility ranges, and the pack list.
193
+ * - **Derived** — the identity, the description, the authors (the top-level
194
+ * `authors` list without its roles), the release addresses, the version, the
195
+ * Foundry and system compatibility ranges, and the pack list.
195
196
  * These are refused if also declared (`description` directly; `descriptionHtml`
196
197
  * is how it is authored) and the two would disagree with nothing to say so.
197
198
  * - **Computed** — namespaced `flags` a repository works out for itself, merged
@@ -8,6 +8,10 @@
8
8
  * failure is timing-dependent, so it survives every run that happens to be
9
9
  * fast enough, which is what makes it worth being explicit about.
10
10
  *
11
+ * **The archive is reproducible.** Entries are written in path order with one
12
+ * date (`SOURCE_DATE_EPOCH` when set, otherwise 1980-01-01) and fixed
13
+ * permissions, so a rebuild of one staged tree is byte-identical.
14
+ *
11
15
  * @param {object} [opts]
12
16
  * @param {string} [opts.stageDir] - The staged package tree.
13
17
  * @param {string} [opts.outDir] - Where the release assets are written.
@@ -15,17 +19,18 @@
15
19
  * Determines both asset names.
16
20
  * @param {string} [opts.metadataDir] - Where the build writes its content
17
21
  * index, consulted when the advertised file was not staged.
18
- * @param {boolean} [opts.pdf] - Whether to build the book that ships beside the
22
+ * @param {boolean} [opts.pdf] - Whether to build the books that ship beside the
19
23
  * archive. `true` by default; `false` skips the build and reports the skip.
20
24
  * @returns {Promise<{zip: string, manifest: string, metadata?: string,
21
- * schema?: string, pdf?: string, pdfFindings: object[], pdfSkipped: string|null,
25
+ * schema?: string, pdfs: string[], pdfFindings: object[], pdfSkipped: string|null,
22
26
  * bytes: number, version: string}>} The paths written, what the book build
23
27
  * found, the archive's size, and the version the manifest declares.
24
28
  * `metadata` is absent when the manifest advertises no content index,
25
- * `schema` is absent when the stage carries no `schema.json`, and `pdf` is
26
- * absent when no book was written. `pdfSkipped` is `null` when a book was
27
- * built, and otherwise the reason none was — itself `null` when the book
28
- * builder could not be loaded, which is reported through `pdfFindings`.
29
+ * `schema` is absent when the stage carries no `schema.json`, and `pdfs` is
30
+ * every book's PDF that was written, in declared order. `pdfSkipped` is
31
+ * `null` when a book was built, and otherwise the reason none was — itself
32
+ * `null` when the book builder could not be loaded or a book failed, which
33
+ * is reported through `pdfFindings`.
29
34
  * @throws {Error} When the stage has no manifest — there is nothing to release,
30
35
  * and an archive without one installs as nothing.
31
36
  */
@@ -40,7 +45,7 @@ export function packRelease({ stageDir, outDir, artifact, metadataDir, pdf, }?:
40
45
  manifest: string;
41
46
  metadata?: string;
42
47
  schema?: string;
43
- pdf?: string;
48
+ pdfs: string[];
44
49
  pdfFindings: object[];
45
50
  pdfSkipped: string | null;
46
51
  bytes: number;
@@ -26,7 +26,8 @@ export class Actors extends SystemActorCompiler {
26
26
  * The body's own compiled item is the model, so a membership carries
27
27
  * whatever that body declares — its subtype, its common skills, its
28
28
  * standings toward others — and the being adds only what is true of *this*
29
- * membership: the rung it stands on and the post it holds.
29
+ * membership: the rung it stands on, the post it holds, and the society it
30
+ * is held in.
30
31
  *
31
32
  * A being naming a body that compiles to no item is a finding rather than a
32
33
  * silently dropped membership: the note says the being belongs somewhere,
@@ -3,8 +3,7 @@
3
3
  * becomes.
4
4
  *
5
5
  * The Item rows are the thirteen types the item registry declares; the one
6
- * Actor row is `being`, which was two types (`character` and `creature`)
7
- * compiling to the same actor before they were retired. Types this
6
+ * Actor row is `being`. Types this
8
7
  * map does not name — `doc`, `macro`, the three map types — compile into
9
8
  * documents that carry no system subtype at all, so they have no row and never
10
9
  * needed one.
@@ -6,13 +6,9 @@ export const COMBAT_TECHNIQUE_STRIKE_MODE: import("../engine/field-spec.mjs").Fi
6
6
  * Every item type's frontmatter vocabulary, in the order the `system` block
7
7
  * emits it.
8
8
  *
9
- * **A field whose shared source is a path into `data:` names both positions.**
10
- * `name` is the source the content format's mapping tables state — `data.seat`,
11
- * `data.weight` — and `legacyKey` is the key the system block still carries, which
12
- * wins while a tree writes it. Naming only the source would move the in-block
13
- * read to a key spelled `data.seat`, which nothing authors; naming only the key
14
- * leaves the container the specification points at unread, and the field then
15
- * compiles its default over an authored value with nothing said.
9
+ * **A field whose shared source is a path into `data:` is authored there and
10
+ * nowhere else.** `name` is the source the content format's mapping tables
11
+ * state — `data.seat`, `data.weight`.
16
12
  *
17
13
  * @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
18
14
  */