@heroiclands/package-build 22.9.1 → 22.10.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.
- package/CHANGELOG.md +54 -0
- package/CONTENT.md +33 -0
- package/bin/content-build.mjs +3 -1
- package/docs/api.md +10 -6
- package/docs/content-format.md +384 -200
- package/docs/project-setup.md +23 -0
- package/engine/actor-compiler.mjs +20 -7
- package/engine/address-fields.mjs +404 -0
- package/engine/address-values.mjs +80 -0
- package/engine/address.mjs +769 -0
- package/engine/art-fields.mjs +112 -97
- package/engine/art-slots.mjs +81 -0
- package/engine/base-compiler.mjs +59 -7
- package/engine/bundle-notes.mjs +5 -2
- package/engine/bundles.mjs +17 -10
- package/engine/content-address.mjs +23 -186
- package/engine/content-embeds.mjs +18 -4
- package/engine/content-format-check.mjs +50 -22
- package/engine/content-index.mjs +85 -43
- package/engine/content-links.mjs +105 -104
- package/engine/content-lint.mjs +11 -1
- package/engine/content-tables.mjs +9 -1
- package/engine/field-spec.mjs +20 -4
- package/engine/folder-notes.mjs +44 -15
- package/engine/foundry-entries.mjs +3 -0
- package/engine/frontmatter-lint.mjs +144 -43
- package/engine/frontmatter.mjs +55 -10
- package/engine/generate.mjs +7 -1
- package/engine/helpers.mjs +37 -9
- package/engine/holdings.mjs +41 -12
- package/engine/index-records.mjs +2 -0
- package/engine/infobox-render.mjs +2 -1
- package/engine/infobox.mjs +26 -6
- package/engine/item-compiler.mjs +5 -0
- package/engine/item-docs.mjs +7 -44
- package/engine/map-dot.mjs +2 -0
- package/engine/map-notes.mjs +7 -2
- package/engine/map-places.mjs +42 -10
- package/engine/metadata-index.mjs +60 -5
- package/engine/note-addresses.mjs +310 -0
- package/engine/note-vocabulary.mjs +145 -26
- package/engine/pdf-build.mjs +1 -1
- package/engine/pdf-toc.mjs +17 -2
- package/engine/place-relations.mjs +160 -50
- package/engine/populations.mjs +17 -9
- package/engine/reference-targets.mjs +54 -0
- package/engine/scenes.mjs +5 -2
- package/engine/shortcode-references.mjs +68 -0
- package/engine/site-build.mjs +39 -15
- package/engine/site-index.mjs +57 -75
- package/engine/sql-tables.mjs +43 -9
- package/engine/system-block.mjs +21 -0
- package/engine/system-vocabulary.mjs +46 -0
- package/engine/web-wikilinks.mjs +11 -9
- package/engine/wikilink-syntax.mjs +21 -0
- package/engine/wikilinks.mjs +92 -306
- package/githooks/attribution.mjs +82 -0
- package/githooks/commit-message.mjs +26 -0
- package/githooks/commit-msg +3 -35
- package/hm3/actors.mjs +8 -24
- package/hm3/item-fields.mjs +15 -5
- package/package.json +1 -1
- package/sohl/infobox.mjs +80 -17
- package/sohl/item-fields.mjs +114 -41
- package/types/engine/address-fields.d.mts +90 -0
- package/types/engine/address-values.d.mts +34 -0
- package/types/engine/address.d.mts +452 -0
- package/types/engine/art-fields.d.mts +59 -53
- package/types/engine/art-slots.d.mts +40 -0
- package/types/engine/base-compiler.d.mts +24 -2
- package/types/engine/bundle-notes.d.mts +2 -2
- package/types/engine/content-address.d.mts +3 -139
- package/types/engine/content-embeds.d.mts +1 -1
- package/types/engine/content-format-check.d.mts +25 -0
- package/types/engine/content-index.d.mts +2 -2
- package/types/engine/content-links.d.mts +6 -5
- package/types/engine/field-spec.d.mts +44 -9
- package/types/engine/folder-notes.d.mts +11 -10
- package/types/engine/frontmatter-lint.d.mts +7 -97
- package/types/engine/frontmatter.d.mts +16 -1
- package/types/engine/helpers.d.mts +2 -2
- package/types/engine/infobox.d.mts +6 -0
- package/types/engine/item-docs.d.mts +1 -35
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/note-addresses.d.mts +41 -0
- package/types/engine/note-vocabulary.d.mts +20 -3
- package/types/engine/place-relations.d.mts +12 -1
- package/types/engine/populations.d.mts +17 -2
- package/types/engine/reference-targets.d.mts +9 -0
- package/types/engine/shortcode-references.d.mts +14 -0
- package/types/engine/site-build.d.mts +3 -3
- package/types/engine/site-index.d.mts +9 -30
- package/types/engine/sql-tables.d.mts +2 -2
- package/types/engine/system-block.d.mts +17 -0
- package/types/engine/system-vocabulary.d.mts +16 -0
- package/types/engine/wikilink-syntax.d.mts +7 -0
- package/types/engine/wikilinks.d.mts +6 -109
- package/types/hm3/item-fields.d.mts +5 -0
- package/types/sohl/infobox.d.mts +4 -5
- package/types/sohl/item-fields.d.mts +8 -0
package/hm3/actors.mjs
CHANGED
|
@@ -26,13 +26,9 @@
|
|
|
26
26
|
* the note says which, by writing `hm3.type`. An absent one is an error naming
|
|
27
27
|
* the note, never a default.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `data.templatePriority` → `flags.hm3.templatePriority` — and every one of
|
|
33
|
-
* them is *declared as that source* rather than as the bare key the corpus
|
|
34
|
-
* writes, so the specification's mapping is executable rather than
|
|
35
|
-
* aspirational. Plus the two anchored prose sections: `{#appearance}` is HM3's
|
|
29
|
+
* Shared gender, occupation and template priority map to HM3's native fields.
|
|
30
|
+
* Native species text is independent of the lore Address in `data.species`.
|
|
31
|
+
* The anchored prose section `{#appearance}` supplies HM3's
|
|
36
32
|
* `description` and `{#dossier}` its
|
|
37
33
|
* `biography`. Everything else an HM3 actor carries — the thirteen abilities,
|
|
38
34
|
* the sunsign, `move`, `fatigue`, `shockIndex`, a creature's `loadRating` — has
|
|
@@ -70,31 +66,19 @@ import {
|
|
|
70
66
|
import { blockField, blockProperty, mergeSystemData } from "../engine/system-block.mjs";
|
|
71
67
|
|
|
72
68
|
/**
|
|
73
|
-
*
|
|
69
|
+
* Native HM3 facts shared by its actor subtypes.
|
|
74
70
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* position, then the shared source, then the default — and so the
|
|
78
|
-
* author-facing reference can be generated from the same statement the compiler
|
|
79
|
-
* obeys.
|
|
80
|
-
*
|
|
81
|
-
* **Both positions, named separately.** The specification maps
|
|
82
|
-
* `data.species` onto `system.species`, and every HM3 note in the corpus writes
|
|
83
|
-
* `hm3.species`. Until the sweep those are two live positions for one
|
|
84
|
-
* field, so the declaration names both: `name` is the shared source, `legacyKey`
|
|
85
|
-
* the key the block still carries, and the block wins while it is there. Naming
|
|
86
|
-
* only one of them is what made the row unreadable — a plain `species` could
|
|
87
|
-
* not see `data.species`, and a dotted `data.species` could not see the 2,512
|
|
88
|
-
* notes that carry `hm3.species`, each yielding `""` in silence.
|
|
71
|
+
* Species is native text. The note's lore Address in `data.species` is
|
|
72
|
+
* independent and supplies no native value.
|
|
89
73
|
*
|
|
90
74
|
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
91
75
|
*/
|
|
92
76
|
const ACTOR_FIELDS = Object.freeze([
|
|
93
77
|
{
|
|
94
|
-
name: "
|
|
95
|
-
legacyKey: "species",
|
|
78
|
+
name: "species",
|
|
96
79
|
to: "species",
|
|
97
80
|
...STRING,
|
|
81
|
+
topLevelMeans: "The shared species is a lore Address, independent of native species text.",
|
|
98
82
|
default: "",
|
|
99
83
|
describe: "The kind of creature this is.",
|
|
100
84
|
},
|
package/hm3/item-fields.mjs
CHANGED
|
@@ -76,7 +76,8 @@ import { AS_AUTHORED, NUMBER } from "../engine/field-spec.mjs";
|
|
|
76
76
|
*/
|
|
77
77
|
const GEAR_COMMON = Object.freeze([
|
|
78
78
|
{
|
|
79
|
-
name: "weight",
|
|
79
|
+
name: "data.weight",
|
|
80
|
+
legacyKey: "weight",
|
|
80
81
|
to: "weight",
|
|
81
82
|
...AS_AUTHORED,
|
|
82
83
|
kind: "number",
|
|
@@ -84,7 +85,8 @@ const GEAR_COMMON = Object.freeze([
|
|
|
84
85
|
describe: "Weight of one, in pounds.",
|
|
85
86
|
},
|
|
86
87
|
{
|
|
87
|
-
name: "value",
|
|
88
|
+
name: "data.value",
|
|
89
|
+
legacyKey: "value",
|
|
88
90
|
to: "value",
|
|
89
91
|
...AS_AUTHORED,
|
|
90
92
|
kind: "number",
|
|
@@ -104,7 +106,8 @@ const GEAR_COMMON = Object.freeze([
|
|
|
104
106
|
* @type {import("../engine/field-spec.mjs").FieldSpec}
|
|
105
107
|
*/
|
|
106
108
|
const QUANTITY = Object.freeze({
|
|
107
|
-
name: "quantity",
|
|
109
|
+
name: "data.quantity",
|
|
110
|
+
legacyKey: "quantity",
|
|
108
111
|
to: "quantity",
|
|
109
112
|
...NUMBER,
|
|
110
113
|
default: 1,
|
|
@@ -124,6 +127,11 @@ const QUANTITY = Object.freeze({
|
|
|
124
127
|
* a registry is addressed by what a note calls itself. What the document is
|
|
125
128
|
* called is the map's answer, and only the map's.
|
|
126
129
|
*
|
|
130
|
+
* Each field names both of its authored positions, as
|
|
131
|
+
* {@link module:hm3/actors} does and for the same reason: `name` is the shared
|
|
132
|
+
* source the content format's mapping tables state, and `legacyKey` is the key
|
|
133
|
+
* the `hm3:` block still carries.
|
|
134
|
+
*
|
|
127
135
|
* @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
|
|
128
136
|
*/
|
|
129
137
|
export const HM3_ITEM_FIELDS = Object.freeze({
|
|
@@ -139,7 +147,8 @@ export const HM3_ITEM_FIELDS = Object.freeze({
|
|
|
139
147
|
containergear: Object.freeze([
|
|
140
148
|
...GEAR_COMMON,
|
|
141
149
|
{
|
|
142
|
-
name: "capacity",
|
|
150
|
+
name: "data.capacity",
|
|
151
|
+
legacyKey: "capacity",
|
|
143
152
|
to: "capacity.max",
|
|
144
153
|
...NUMBER,
|
|
145
154
|
default: 0,
|
|
@@ -161,7 +170,8 @@ export const HM3_ITEM_FIELDS = Object.freeze({
|
|
|
161
170
|
// through, and only the mastery level is declared here.
|
|
162
171
|
skill: Object.freeze([
|
|
163
172
|
{
|
|
164
|
-
name: "masteryLevel",
|
|
173
|
+
name: "data.masteryLevel",
|
|
174
|
+
legacyKey: "masteryLevel",
|
|
165
175
|
to: "masteryLevel",
|
|
166
176
|
...NUMBER,
|
|
167
177
|
default: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.10.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
package/sohl/infobox.mjs
CHANGED
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
* @module
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
+
import { parseAddress, isAddressTuple, completeAddress } from "../engine/address.mjs";
|
|
55
|
+
import { contentPackage } from "../engine/content-package.mjs";
|
|
54
56
|
import {
|
|
55
57
|
DURATION_LABELS,
|
|
56
58
|
GEAR_UNITS,
|
|
@@ -63,6 +65,8 @@ import {
|
|
|
63
65
|
import { currentType } from "../engine/ids.mjs";
|
|
64
66
|
import { subTypes } from "../engine/note-vocabulary.mjs";
|
|
65
67
|
import { systemBlock, systemData } from "../engine/system-block.mjs";
|
|
68
|
+
import { readCanonicalKey } from "../engine/content-address.mjs";
|
|
69
|
+
import { readQualifier } from "../engine/address.mjs";
|
|
66
70
|
import { NOTE_SCHEMAS } from "./note-schemas.mjs";
|
|
67
71
|
import { GEAR_TYPE_TO_KEY } from "./being-info.mjs";
|
|
68
72
|
|
|
@@ -174,15 +178,45 @@ function isMapping(value) {
|
|
|
174
178
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Every content type `model:` may name — the note vocabulary this system
|
|
183
|
+
* declares, engine types included.
|
|
184
|
+
*
|
|
185
|
+
* @type {ReadonlySet<string>}
|
|
186
|
+
*/
|
|
187
|
+
const MODEL_TYPES = new Set(Object.keys(NOTE_SCHEMAS));
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The `(type, shortcode)` an entry's `model:` names, or `undefined`.
|
|
191
|
+
*
|
|
192
|
+
* `model` is an Address, read by the same grammar every wikilink is —
|
|
193
|
+
* `type-shortcode` within this package, `package-system-type-shortcode` to
|
|
194
|
+
* name a dependency's catalogue. This decode answers neither of the two
|
|
195
|
+
* questions {@link module:engine/address} asks about the package: a runtime
|
|
196
|
+
* lookup among one actor's embedded items resolves by `(type, shortcode)`
|
|
197
|
+
* alone, where packages do not exist, so a fully qualified `model` is read for
|
|
198
|
+
* its last two segments exactly as a bare one is.
|
|
199
|
+
*
|
|
200
|
+
* @param {unknown} model - The authored value.
|
|
201
|
+
* @returns {{type: string, shortcode: string}|undefined} What it names.
|
|
202
|
+
*/
|
|
203
|
+
function modelAddress(model) {
|
|
204
|
+
const target = parseAddress(
|
|
205
|
+
model,
|
|
206
|
+
{ package: contentPackage(), system: "sohl", types: MODEL_TYPES },
|
|
207
|
+
{ declared: true, legacyShortcodeCase: true },
|
|
208
|
+
);
|
|
209
|
+
return target.reason ? undefined : target;
|
|
210
|
+
}
|
|
211
|
+
|
|
177
212
|
/**
|
|
178
213
|
* What one entry of `sohl.items` names.
|
|
179
214
|
*
|
|
180
215
|
* An entry addresses its item three ways and every tree uses all three: a
|
|
181
|
-
* `model` address
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* which form a note happened to use.
|
|
216
|
+
* `model` address, explicit `type` / `shortcode` keys, or a locally authored
|
|
217
|
+
* item stating its type with the shortcode inside its own `system` block. One
|
|
218
|
+
* decode covers all of them, so nothing downstream has to know which form a
|
|
219
|
+
* note happened to use.
|
|
186
220
|
*
|
|
187
221
|
* @param {object} entry - One `sohl.items` entry.
|
|
188
222
|
* @returns {{type: string, shortcode: string, name: string, system: object}|undefined}
|
|
@@ -190,19 +224,21 @@ function isMapping(value) {
|
|
|
190
224
|
*/
|
|
191
225
|
export function decodeItem(entry) {
|
|
192
226
|
if (!isMapping(entry)) return undefined;
|
|
227
|
+
const address = entry.model ? modelAddress(entry.model) : undefined;
|
|
193
228
|
let type = typeof entry.type === "string" ? entry.type : "";
|
|
194
229
|
let shortcode = typeof entry.shortcode === "string" ? entry.shortcode : "";
|
|
195
|
-
if (!type
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
type =
|
|
199
|
-
if (!shortcode) shortcode =
|
|
230
|
+
if (!type) {
|
|
231
|
+
const named = address;
|
|
232
|
+
if (named) {
|
|
233
|
+
type = named.type;
|
|
234
|
+
if (!shortcode) shortcode = named.shortcode;
|
|
200
235
|
}
|
|
201
236
|
}
|
|
202
237
|
const system = isMapping(entry.system) ? entry.system : {};
|
|
203
238
|
if (!shortcode && typeof system.shortcode === "string") shortcode = system.shortcode;
|
|
204
239
|
if (!type) return undefined;
|
|
205
240
|
return {
|
|
241
|
+
...(address ? { address } : {}),
|
|
206
242
|
type: currentType(type),
|
|
207
243
|
shortcode,
|
|
208
244
|
name: typeof entry.name === "string" ? entry.name : "",
|
|
@@ -222,6 +258,32 @@ function itemsOf(fm, block) {
|
|
|
222
258
|
return (Array.isArray(declared) ? declared : []).map(decodeItem).filter(Boolean);
|
|
223
259
|
}
|
|
224
260
|
|
|
261
|
+
/**
|
|
262
|
+
* A model's documentation presentation, with its native name as plain text
|
|
263
|
+
* when no documentation is published. The model keeps its native identity.
|
|
264
|
+
* @param {object} item - Decoded native item.
|
|
265
|
+
* @param {Function} resolve - The medium's reference resolver.
|
|
266
|
+
* @param {string} block - The native system.
|
|
267
|
+
* @returns {object|undefined} Documentation links and display metadata.
|
|
268
|
+
*/
|
|
269
|
+
function itemPresentation(item, resolve, block) {
|
|
270
|
+
if (!item.shortcode) return undefined;
|
|
271
|
+
if (!item.address)
|
|
272
|
+
return resolve?.(item.shortcode, { kind: "shortcode", type: item.type, system: block });
|
|
273
|
+
const documentation = completeAddress({ ...item.address, system: "none" });
|
|
274
|
+
const found = resolve?.(documentation, { type: documentation.type });
|
|
275
|
+
const native =
|
|
276
|
+
!found?.name || !found?.subType ? resolve?.(item.address, { type: item.type }) : undefined;
|
|
277
|
+
if (!found && !native) return undefined;
|
|
278
|
+
return {
|
|
279
|
+
name: found?.name ?? native?.name,
|
|
280
|
+
subType: found?.subType ?? native?.subType,
|
|
281
|
+
address: documentation,
|
|
282
|
+
...(found?.url ? { url: found.url } : {}),
|
|
283
|
+
...(found?.uuid ? { uuid: found.uuid } : {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
225
287
|
/**
|
|
226
288
|
* One item as a value a renderer can draw: its name, and a link where the
|
|
227
289
|
* index reached it.
|
|
@@ -229,10 +291,11 @@ function itemsOf(fm, block) {
|
|
|
229
291
|
* @param {object} item - A decoded item.
|
|
230
292
|
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
231
293
|
* medium's resolver.
|
|
232
|
-
* @
|
|
294
|
+
* @param {string} block - The native system.
|
|
295
|
+
* @returns {{text: string, url?: string, uuid?: string, address?: import("../engine/address.mjs").AddressTuple}} The value.
|
|
233
296
|
*/
|
|
234
|
-
function itemValue(item, resolve) {
|
|
235
|
-
const found = item
|
|
297
|
+
function itemValue(item, resolve, block) {
|
|
298
|
+
const found = itemPresentation(item, resolve, block);
|
|
236
299
|
const value = { text: item.name || found?.name || humanizeValue(item.shortcode) };
|
|
237
300
|
if (found?.url) value.url = found.url;
|
|
238
301
|
if (found?.uuid) value.uuid = found.uuid;
|
|
@@ -277,9 +340,9 @@ export function beingSections(fm, { block, resolve }) {
|
|
|
277
340
|
if (item.type !== "skill") continue;
|
|
278
341
|
const mastery = item.system.masteryLevelBase;
|
|
279
342
|
if (!hasValue(mastery)) continue;
|
|
280
|
-
const found = item
|
|
343
|
+
const found = itemPresentation(item, resolve, block);
|
|
281
344
|
const family = item.system.subType ?? found?.subType;
|
|
282
|
-
const value = itemValue(item, resolve);
|
|
345
|
+
const value = itemValue(item, resolve, block);
|
|
283
346
|
const entry = { ...value, text: `${value.text} ${mastery}` };
|
|
284
347
|
if (family && families.has(family)) families.get(family).push(entry);
|
|
285
348
|
else unplaced.push(entry);
|
|
@@ -298,7 +361,7 @@ export function beingSections(fm, { block, resolve }) {
|
|
|
298
361
|
const mystical = [];
|
|
299
362
|
for (const item of items) {
|
|
300
363
|
if (item.type !== "mysticalability") continue;
|
|
301
|
-
mystical.push(itemValue(item, resolve));
|
|
364
|
+
mystical.push(itemValue(item, resolve, block));
|
|
302
365
|
}
|
|
303
366
|
if (mystical.length) {
|
|
304
367
|
sections.push({
|
|
@@ -313,7 +376,7 @@ export function beingSections(fm, { block, resolve }) {
|
|
|
313
376
|
for (const item of items) {
|
|
314
377
|
const key = GEAR_TYPE_TO_KEY[item.type];
|
|
315
378
|
if (!key) continue;
|
|
316
|
-
gear.get(key).push(itemValue(item, resolve));
|
|
379
|
+
gear.get(key).push(itemValue(item, resolve, block));
|
|
317
380
|
}
|
|
318
381
|
const gearGroups = [];
|
|
319
382
|
for (const [key, entries] of gear) {
|