@heroiclands/package-build 0.6.1 → 3.0.1

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 (127) hide show
  1. package/CHANGELOG-content-build.md +965 -0
  2. package/CHANGELOG.md +68 -0
  3. package/CONTENT.md +824 -0
  4. package/MIGRATING.md +127 -0
  5. package/README.md +29 -17
  6. package/bin/content-build.mjs +1082 -0
  7. package/bin/package-build.mjs +2 -2
  8. package/bin/report.mjs +2 -2
  9. package/config.mjs +21 -18
  10. package/content-config.mjs +1424 -0
  11. package/coverage.mjs +1 -1
  12. package/e2e.mjs +1 -1
  13. package/engine/abbreviations.mjs +0 -0
  14. package/engine/base-compiler.mjs +608 -0
  15. package/engine/code-fences.mjs +311 -0
  16. package/engine/compendiums.mjs +353 -0
  17. package/engine/content-address.mjs +141 -0
  18. package/engine/content-links.mjs +438 -0
  19. package/engine/content-lint.mjs +204 -0
  20. package/engine/content-package.mjs +67 -0
  21. package/engine/content-slug.mjs +169 -0
  22. package/engine/content-tables.mjs +1216 -0
  23. package/engine/content-tree.mjs +56 -0
  24. package/engine/diagnostics.mjs +270 -0
  25. package/engine/field-reference.mjs +242 -0
  26. package/engine/field-spec.mjs +218 -0
  27. package/engine/foreign-catalog.mjs +450 -0
  28. package/engine/foreign-manifests.mjs +129 -0
  29. package/engine/frontmatter-lint.mjs +343 -0
  30. package/engine/frontmatter.mjs +258 -0
  31. package/engine/generate.mjs +305 -0
  32. package/engine/helpers.mjs +809 -0
  33. package/engine/ids.mjs +192 -0
  34. package/engine/index.mjs +142 -0
  35. package/engine/item-docs.mjs +153 -0
  36. package/engine/item-registry.mjs +129 -0
  37. package/engine/journals.mjs +374 -0
  38. package/engine/kb-manifest.mjs +473 -0
  39. package/engine/macros.mjs +333 -0
  40. package/engine/manifest-emit.mjs +339 -0
  41. package/engine/map-notes.mjs +1302 -0
  42. package/engine/pack-config.mjs +479 -0
  43. package/engine/pack-router.mjs +254 -0
  44. package/engine/prose-config.mjs +238 -0
  45. package/engine/prose-lint.mjs +294 -0
  46. package/engine/region-events.mjs +79 -0
  47. package/engine/scene-levels.mjs +420 -0
  48. package/engine/scenes.mjs +528 -0
  49. package/engine/site-build.mjs +778 -0
  50. package/engine/site-index.mjs +332 -0
  51. package/engine/web-wikilinks.mjs +330 -0
  52. package/engine/wikilink-syntax.mjs +105 -0
  53. package/engine/wikilinks.mjs +645 -0
  54. package/index.mjs +42 -25
  55. package/lang.mjs +1 -1
  56. package/manifest.mjs +79 -5
  57. package/markdownlint-config.mjs +45 -0
  58. package/package.json +78 -35
  59. package/prettier-config.mjs +34 -0
  60. package/sohl/actors.mjs +581 -0
  61. package/sohl/affiliation-standings.mjs +44 -0
  62. package/sohl/being-info.mjs +173 -0
  63. package/sohl/default-item-art.mjs +82 -0
  64. package/sohl/index.mjs +55 -0
  65. package/sohl/item-builders.mjs +114 -0
  66. package/sohl/item-fields.mjs +867 -0
  67. package/sohl/items.mjs +197 -0
  68. package/sohl/kb-passes.mjs +207 -0
  69. package/sohl/note-schemas.mjs +322 -0
  70. package/templates.mjs +1 -1
  71. package/types/config.d.mts +1 -1
  72. package/types/content-config.d.mts +571 -0
  73. package/types/engine/abbreviations.d.mts +44 -0
  74. package/types/engine/base-compiler.d.mts +320 -0
  75. package/types/engine/code-fences.d.mts +102 -0
  76. package/types/engine/compendiums.d.mts +77 -0
  77. package/types/engine/content-address.d.mts +61 -0
  78. package/types/engine/content-links.d.mts +87 -0
  79. package/types/engine/content-lint.d.mts +47 -0
  80. package/types/engine/content-package.d.mts +36 -0
  81. package/types/engine/content-slug.d.mts +59 -0
  82. package/types/engine/content-tables.d.mts +131 -0
  83. package/types/engine/content-tree.d.mts +12 -0
  84. package/types/engine/diagnostics.d.mts +152 -0
  85. package/types/engine/field-reference.d.mts +20 -0
  86. package/types/engine/field-spec.d.mts +181 -0
  87. package/types/engine/foreign-catalog.d.mts +103 -0
  88. package/types/engine/foreign-manifests.d.mts +43 -0
  89. package/types/engine/frontmatter-lint.d.mts +74 -0
  90. package/types/engine/frontmatter.d.mts +99 -0
  91. package/types/engine/generate.d.mts +56 -0
  92. package/types/engine/helpers.d.mts +348 -0
  93. package/types/engine/ids.d.mts +126 -0
  94. package/types/engine/index.d.mts +33 -0
  95. package/types/engine/item-docs.d.mts +73 -0
  96. package/types/engine/item-registry.d.mts +57 -0
  97. package/types/engine/journals.d.mts +121 -0
  98. package/types/engine/kb-manifest.d.mts +214 -0
  99. package/types/engine/macros.d.mts +201 -0
  100. package/types/engine/manifest-emit.d.mts +137 -0
  101. package/types/engine/map-notes.d.mts +283 -0
  102. package/types/engine/pack-config.d.mts +77 -0
  103. package/types/engine/pack-router.d.mts +53 -0
  104. package/types/engine/prose-config.d.mts +121 -0
  105. package/types/engine/prose-lint.d.mts +59 -0
  106. package/types/engine/region-events.d.mts +43 -0
  107. package/types/engine/scene-levels.d.mts +36 -0
  108. package/types/engine/scenes.d.mts +48 -0
  109. package/types/engine/site-build.d.mts +214 -0
  110. package/types/engine/site-index.d.mts +141 -0
  111. package/types/engine/web-wikilinks.d.mts +67 -0
  112. package/types/engine/wikilink-syntax.d.mts +101 -0
  113. package/types/engine/wikilinks.d.mts +172 -0
  114. package/types/index.d.mts +3 -0
  115. package/types/manifest.d.mts +52 -4
  116. package/types/markdownlint-config.d.mts +8 -0
  117. package/types/prettier-config.d.mts +1 -0
  118. package/types/sohl/actors.d.mts +65 -0
  119. package/types/sohl/affiliation-standings.d.mts +22 -0
  120. package/types/sohl/being-info.d.mts +78 -0
  121. package/types/sohl/default-item-art.d.mts +28 -0
  122. package/types/sohl/index.d.mts +7 -0
  123. package/types/sohl/item-builders.d.mts +11 -0
  124. package/types/sohl/item-fields.d.mts +20 -0
  125. package/types/sohl/items.d.mts +19 -0
  126. package/types/sohl/kb-passes.d.mts +71 -0
  127. package/types/sohl/note-schemas.d.mts +7 -0
@@ -0,0 +1,322 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
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
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /**
15
+ * What every SoHL content type may write under `sohl:` — the vocabulary the
16
+ * frontmatter linter checks a note against (#19).
17
+ *
18
+ * **Item types need no declaration here.** {@link ITEM_FIELDS} already is one,
19
+ * and it is the same list the compiler obeys, so an item's schema and its
20
+ * builder cannot disagree — they are one object. The linter simply reads it.
21
+ *
22
+ * **The other types are declared, because their compilers are hand-written.**
23
+ * A being, a macro, a journal note and the three map types are built by code
24
+ * that reads frontmatter directly rather than from a field list, so their
25
+ * vocabulary has to be *stated* to be checkable. These entries carry no `to`:
26
+ * nothing here builds anything, and claiming an emitted path they do not
27
+ * produce would be a lie in the one place a reader would trust it. When one of
28
+ * those compilers becomes declarative, its entry here becomes the builder, the
29
+ * way `ITEM_FIELDS` did (#22).
30
+ *
31
+ * **Every consumer loads all of it.** An adventure module authors the full
32
+ * vocabulary — a specific skill, a magic sword, an NPC, a custom beast is the
33
+ * normal shape of an adventure — so nothing is withheld from one. The
34
+ * `sohl/` ÷ `engine/` line is between knowledge of the *game system* and
35
+ * knowledge of the *note format*, not a permission boundary between consumers.
36
+ *
37
+ * @module
38
+ */
39
+
40
+ import { AS_AUTHORED, NUMBER, STRING } from "../engine/field-spec.mjs";
41
+ import { ITEM_FIELDS } from "./item-fields.mjs";
42
+
43
+ /** A map-valued property, whose entries the compiler walks by key. */
44
+ const MAP = Object.freeze({ shape: "map of key → entry", kind: "map" });
45
+
46
+ /** A list-valued property. */
47
+ const LIST = Object.freeze({ shape: "list", kind: "list" });
48
+
49
+ /**
50
+ * A `doc` note — free prose compiled into a JournalEntry.
51
+ *
52
+ * Empty on purpose: a documentation note carries the frontmatter envelope every
53
+ * note carries, and nothing under `sohl:` beyond the universal keys the linter
54
+ * allows for any type. Declaring the type with no fields is what distinguishes
55
+ * a type with no vocabulary apart from a type that is unknown, which are
56
+ * different findings.
57
+ *
58
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
59
+ */
60
+ const DOC_FIELDS = Object.freeze([]);
61
+
62
+ /**
63
+ * A `macro` note — a script compiled into a Foundry Macro.
64
+ *
65
+ * Neither field is required: the compiler defaults `macroType` to `script` and
66
+ * `macroScope` to `global`, and both reject an unrecognised value outright, so
67
+ * the value check here is about shape and the compiler's is about membership.
68
+ *
69
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
70
+ */
71
+ const MACRO_FIELDS = Object.freeze([
72
+ {
73
+ name: "macroType",
74
+ ...STRING,
75
+ describe: 'What the macro is — "script" or "chat".',
76
+ },
77
+ {
78
+ name: "macroScope",
79
+ ...STRING,
80
+ describe: "Which scope the macro is registered in.",
81
+ },
82
+ ]);
83
+
84
+ /**
85
+ * A `being` note — an actor, with its body, movement and embedded items.
86
+ *
87
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
88
+ */
89
+ const BEING_FIELDS = Object.freeze([
90
+ {
91
+ name: "body",
92
+ ...MAP,
93
+ describe: "The being's body structure, by part shortcode.",
94
+ },
95
+ {
96
+ name: "attributes",
97
+ ...MAP,
98
+ describe: "Attribute scores, by attribute shortcode.",
99
+ },
100
+ {
101
+ name: "items",
102
+ ...LIST,
103
+ describe:
104
+ "Items embedded on the being, each addressed by (type, shortcode) or carrying enough fields to stand alone.",
105
+ },
106
+ {
107
+ name: "currentMoveMedium",
108
+ ...STRING,
109
+ describe: "Which medium the being is currently moving through.",
110
+ },
111
+ {
112
+ name: "movementProfiles",
113
+ ...LIST,
114
+ describe: "Movement rates, one profile per medium.",
115
+ },
116
+ {
117
+ name: "defaultCombatGroup",
118
+ ...AS_AUTHORED,
119
+ describe: "The combat group the being joins by default.",
120
+ },
121
+ ]);
122
+
123
+ /**
124
+ * A map note — `battlemap`, `localmap` or `regionalmap`, each compiled into a
125
+ * Foundry Scene.
126
+ *
127
+ * The three differ only in derived canvas defaults, which is the map compiler's
128
+ * business; their authored vocabulary is the same, so they share one
129
+ * declaration rather than three copies that could drift.
130
+ *
131
+ * `image` is the one required field — the compiler refuses a map note without
132
+ * it, since a scene with no background is not a map.
133
+ *
134
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
135
+ */
136
+ const MAP_FIELDS = Object.freeze([
137
+ {
138
+ name: "image",
139
+ ...STRING,
140
+ required: true,
141
+ describe: "The scene's background image.",
142
+ },
143
+ {
144
+ name: "dimensions",
145
+ ...LIST,
146
+ required: true,
147
+ describe: "`[width, height]` in whole pixels — the map art's own size.",
148
+ },
149
+ {
150
+ name: "pxPerGrid",
151
+ ...NUMBER,
152
+ required: true,
153
+ describe:
154
+ "Whole pixels per grid square. Must match the art, so the compiler refuses a map note without it.",
155
+ },
156
+ {
157
+ name: "navName",
158
+ ...STRING,
159
+ describe: "The scene's short name in Foundry's navigation bar.",
160
+ },
161
+ {
162
+ name: "levelName",
163
+ ...STRING,
164
+ describe: "Name of the ground level.",
165
+ },
166
+ {
167
+ name: "backgroundColor",
168
+ ...STRING,
169
+ describe: "Colour shown beyond the background image.",
170
+ },
171
+ {
172
+ name: "overlay",
173
+ ...STRING,
174
+ describe: "Foreground image drawn over the scene.",
175
+ },
176
+ { name: "walls", ...MAP, describe: "Wall segments, by key." },
177
+ { name: "doors", ...MAP, describe: "Doors, by key." },
178
+ { name: "lights", ...MAP, describe: "Light sources, by key." },
179
+ { name: "tiles", ...MAP, describe: "Tiles, by key." },
180
+ { name: "sounds", ...MAP, describe: "Ambient sounds, by key." },
181
+ {
182
+ name: "locations",
183
+ ...MAP,
184
+ describe: "Map notes pinned to the scene, by key.",
185
+ },
186
+ {
187
+ name: "regions",
188
+ ...MAP,
189
+ describe: "Regions and their behaviours, by key.",
190
+ },
191
+ {
192
+ name: "place",
193
+ ...AS_AUTHORED,
194
+ describe: "The place this map depicts.",
195
+ },
196
+ {
197
+ name: "placeName",
198
+ ...STRING,
199
+ describe: "Display name of the place this map depicts.",
200
+ },
201
+ ]);
202
+
203
+ /**
204
+ * Authored, but not compiled — vocabulary the *presentation* surfaces read.
205
+ *
206
+ * A note feeds a knowledgebase and a website as well as a compendium pack, and
207
+ * those consume classification the pack build never emits: a knowledgebase list
208
+ * page groups armour by `armorType` and gear by `craft`, thalorna's site reads
209
+ * `traits` on 163 notes. None of it appears in {@link ITEM_FIELDS}, because
210
+ * that list is the *builder* and these are not built.
211
+ *
212
+ * They are declared here anyway, because the question this schema answers is
213
+ * "what may a note of this type write", not "what does the compiler emit". The
214
+ * distinction is not academic: treating the builder's allow-list as the whole
215
+ * vocabulary reported 4,241 unknown properties against SoHL's own tree, every
216
+ * one of them correctly authored.
217
+ *
218
+ * Each carries no `kind` unless its shape is certain, so the lint reports a
219
+ * misspelling without making a claim about a value it does not consume.
220
+ *
221
+ * @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
222
+ */
223
+ const PRESENTATION_FIELDS = Object.freeze({
224
+ armorgear: Object.freeze([
225
+ {
226
+ name: "craft",
227
+ ...MAP,
228
+ describe:
229
+ "The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
230
+ },
231
+ {
232
+ name: "armorType",
233
+ ...STRING,
234
+ describe: "Armour class, for knowledgebase grouping.",
235
+ },
236
+ {
237
+ name: "detailMaterial",
238
+ ...AS_AUTHORED,
239
+ describe: "Material detail shown on the knowledgebase page.",
240
+ },
241
+ ]),
242
+ weapongear: Object.freeze([
243
+ {
244
+ name: "craft",
245
+ ...MAP,
246
+ describe:
247
+ "The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
248
+ },
249
+ {
250
+ name: "weaponType",
251
+ ...STRING,
252
+ describe: "Weapon class, for site grouping.",
253
+ },
254
+ ]),
255
+ miscgear: Object.freeze([
256
+ {
257
+ name: "craft",
258
+ ...MAP,
259
+ describe:
260
+ "The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
261
+ },
262
+ ]),
263
+ containergear: Object.freeze([
264
+ {
265
+ name: "craft",
266
+ ...MAP,
267
+ describe:
268
+ "The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
269
+ },
270
+ ]),
271
+ projectilegear: Object.freeze([
272
+ {
273
+ name: "craft",
274
+ ...MAP,
275
+ describe:
276
+ "The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
277
+ },
278
+ {
279
+ name: "traits",
280
+ ...AS_AUTHORED,
281
+ describe: "Descriptive traits, read by the publishing sites.",
282
+ },
283
+ ]),
284
+ skill: Object.freeze([
285
+ {
286
+ name: "strikeMode",
287
+ ...AS_AUTHORED,
288
+ describe:
289
+ "The strike mode a combat technique declares. Compiled, but applied by the builder rather than listed in the type's fields.",
290
+ },
291
+ ]),
292
+ being: Object.freeze([
293
+ {
294
+ name: "attrRollFormula",
295
+ ...AS_AUTHORED,
296
+ describe: "Formula shown for the being's attribute rolls.",
297
+ },
298
+ ]),
299
+ });
300
+
301
+ /**
302
+ * Every content type this package compiles, and what a note of that type may
303
+ * write.
304
+ *
305
+ * @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
306
+ */
307
+ export const NOTE_SCHEMAS = Object.freeze({
308
+ ...Object.fromEntries(
309
+ Object.entries(ITEM_FIELDS).map(([type, fields]) => [
310
+ type,
311
+ // A new list, never a mutation: `ITEM_FIELDS[type]` is the identity
312
+ // the builder registry holds, and the two must stay the same object.
313
+ Object.freeze([...fields, ...(PRESENTATION_FIELDS[type] ?? [])]),
314
+ ]),
315
+ ),
316
+ doc: DOC_FIELDS,
317
+ macro: MACRO_FIELDS,
318
+ being: Object.freeze([...BEING_FIELDS, ...PRESENTATION_FIELDS.being]),
319
+ battlemap: MAP_FIELDS,
320
+ localmap: MAP_FIELDS,
321
+ regionalmap: MAP_FIELDS,
322
+ });
package/templates.mjs CHANGED
@@ -40,7 +40,7 @@
40
40
  */
41
41
 
42
42
  import Handlebars from "handlebars";
43
- import { positionOfLiteral } from "@heroiclands/content-build/engine/diagnostics";
43
+ import { positionOfLiteral } from "./engine/diagnostics.mjs";
44
44
 
45
45
  /**
46
46
  * A single finding, in the fields the shared diagnostic format takes.
@@ -68,7 +68,7 @@
68
68
  * disk. {@link loadPackageBuildConfig} is the same function with the loading
69
69
  * put back.
70
70
  *
71
- * @param {object} shared - The resolved content-build configuration.
71
+ * @param {object} shared - The resolved content configuration.
72
72
  * @returns {Readonly<PackageBuildConfig>} The frozen configuration.
73
73
  * @throws {TypeError} When the reserved section declares something malformed.
74
74
  */