@heroiclands/package-build 17.1.0 → 18.0.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 +1773 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +394 -111
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +78 -81
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/labels.mjs +191 -0
- package/manifest.mjs +43 -2
- package/package.json +19 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +64 -80
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +16 -38
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
package/sohl/actors.mjs
CHANGED
|
@@ -12,8 +12,16 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* **SoHL's Actor pass** — what a SoHL `being` document holds, and nothing else.
|
|
16
|
+
*
|
|
17
|
+
* The machinery an actor of any system needs — the predefined-item catalogue,
|
|
18
|
+
* reference translation through the system's map, embedding and its stable ids,
|
|
19
|
+
* the anchored prose sections — lives in
|
|
20
|
+
* {@link module:engine/actor-compiler}, where a second system reaches it
|
|
21
|
+
* (#139). What is left here is SoHL's data model: the body structure and its
|
|
22
|
+
* movement profiles, the attributes-and-items frontmatter that becomes embedded
|
|
23
|
+
* documents, the opening mastery level a skill is baked with, and the `system`
|
|
24
|
+
* block itself.
|
|
17
25
|
*
|
|
18
26
|
* One content type today, and the actor subtype it produces is **declared**
|
|
19
27
|
* rather than assumed to be the same word: `sohl/document-subtypes.mjs` maps
|
|
@@ -26,62 +34,31 @@
|
|
|
26
34
|
* SoHL#1580 and are now reported by `assertTypeNotRetired` in
|
|
27
35
|
* `engine/ids.mjs`.
|
|
28
36
|
*
|
|
29
|
-
* Each actor's embedded items are resolved by looking `(type, shortcode)` up
|
|
30
|
-
* against the generated JSON tree of every Item pack (built in prior items
|
|
31
|
-
* passes and named by the caller as `itemsSourceDirs`). All of them, because a
|
|
32
|
-
* repository may group its items into several Item packs (#1566) and a being
|
|
33
|
-
* may hold items from any of them.
|
|
34
37
|
* Attributes (`sohl.attributes` map) become embedded attribute items with
|
|
35
38
|
* `scoreBase` set from the map value. Each entry in `sohl.items` is similarly
|
|
36
39
|
* resolved by `(type, shortcode)` and deep-merged with the entry's other
|
|
37
40
|
* properties. `sohl.skills` is ignored.
|
|
38
41
|
*
|
|
39
|
-
* **Those references are in the note vocabulary and the addresses are in the
|
|
40
|
-
* document's**, and the difference is stated rather than assumed away (#140):
|
|
41
|
-
* {@link itemAddress} keys a predefined item by the subtype its compiled
|
|
42
|
-
* document carries, and {@link Actors#embeddedSubtype} translates each
|
|
43
|
-
* authored reference forward through the system's map before the lookup. A
|
|
44
|
-
* reference that then resolves to nothing is a finding naming the note and the
|
|
45
|
-
* reference — never an item quietly missing from the compiled actor.
|
|
46
|
-
*
|
|
47
42
|
* Not a standalone script — exports the `Actors` compiler class, imported and
|
|
48
|
-
* driven by `
|
|
49
|
-
*
|
|
43
|
+
* driven by `engine/generate.mjs`. Must run after the items passes, since it
|
|
44
|
+
* reads their generated JSON trees.
|
|
50
45
|
*
|
|
51
|
-
*
|
|
52
|
-
* wikilinks, writing the JSON and counting errors — belongs to {@link sohl.utils.packs.BasePackCompiler}; this module
|
|
53
|
-
* states only what makes this pass its own (#1509).
|
|
46
|
+
* @module
|
|
54
47
|
*/
|
|
55
48
|
|
|
56
|
-
import fs from "fs";
|
|
57
|
-
import path from "path";
|
|
58
|
-
import log from "loglevel";
|
|
59
|
-
|
|
60
49
|
import {
|
|
61
50
|
sohlField,
|
|
62
|
-
getFrontmatter,
|
|
63
|
-
makeId,
|
|
64
51
|
resolveName,
|
|
65
52
|
resolveImg,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
md,
|
|
53
|
+
systemTemplatePriority,
|
|
54
|
+
folderField,
|
|
69
55
|
} from "../engine/helpers.mjs";
|
|
70
|
-
import { emitDiagnostic } from "../engine/diagnostics.mjs";
|
|
71
56
|
import { openingMasteryLevel } from "./skill-base.mjs";
|
|
72
|
-
import {
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
// inferred from the type itself (#79).
|
|
77
|
-
import {
|
|
78
|
-
documentSubtype,
|
|
79
|
-
mapsNoteType,
|
|
80
|
-
noteTypesFor,
|
|
81
|
-
referencedSubtype,
|
|
82
|
-
} from "../engine/document-subtypes.mjs";
|
|
57
|
+
import { SystemActorCompiler, renderSection } from "../engine/actor-compiler.mjs";
|
|
58
|
+
// Which Foundry Actor subtype a note's `type` compiles into. Looked up in the
|
|
59
|
+
// system's declared map, never inferred from the type itself (#79).
|
|
60
|
+
import { documentSubtype } from "../engine/document-subtypes.mjs";
|
|
83
61
|
import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
84
|
-
import { locateFrontmatterKey } from "../engine/retired-fields.mjs";
|
|
85
62
|
// The note-level `sohl:` block: `sohl.system` onto the document's `system`
|
|
86
63
|
// verbatim, and `sohl.img` / `sohl.effects` / `sohl.flags` overriding their
|
|
87
64
|
// shared top-level forms for this system alone (#58).
|
|
@@ -97,19 +74,6 @@ import { blockProperty, mergeSystemData } from "../engine/system-block.mjs";
|
|
|
97
74
|
*/
|
|
98
75
|
const SYSTEM = SOHL_DOCUMENT_SUBTYPES.block;
|
|
99
76
|
|
|
100
|
-
/**
|
|
101
|
-
* The note types this pass claims — every one the system's map sends to an
|
|
102
|
-
* `Actor`.
|
|
103
|
-
*
|
|
104
|
-
* Read from the map rather than restated as a constant. It *was* a constant:
|
|
105
|
-
* `ACTOR_VAULT_TYPE = "being"` sat here and `type: "being"` was emitted several
|
|
106
|
-
* hundred lines below, with nothing relating them, so a change to either
|
|
107
|
-
* followed the other only by coincidence (#79).
|
|
108
|
-
*
|
|
109
|
-
* @type {readonly string[]}
|
|
110
|
-
*/
|
|
111
|
-
const ACTOR_NOTE_TYPES = Object.freeze(noteTypesFor(SOHL_DOCUMENT_SUBTYPES, "Actor"));
|
|
112
|
-
|
|
113
77
|
// Default art per actor **subtype**, applied when frontmatter supplies no `img`
|
|
114
78
|
// / `portrait`. Beings default to the generic person icon; another subtype adds
|
|
115
79
|
// its own entry here as the map gains a row for it.
|
|
@@ -139,43 +103,6 @@ function defaultActorImg(subType) {
|
|
|
139
103
|
return img;
|
|
140
104
|
}
|
|
141
105
|
|
|
142
|
-
/**
|
|
143
|
-
* Strip compendium-only fields from a predefined item before embedding it
|
|
144
|
-
* inside an actor's `items[]`. These fields belong on a top-level
|
|
145
|
-
* compendium document, not on an embedded one.
|
|
146
|
-
*/
|
|
147
|
-
function stripCompendiumFields(item) {
|
|
148
|
-
// eslint-disable-next-line no-unused-vars
|
|
149
|
-
const { _key, _stats, ownership, folder, ...rest } = item;
|
|
150
|
-
return rest;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Recursively merge `overlay` onto `base`. Plain objects merge key-by-key;
|
|
155
|
-
* everything else (arrays, primitives, null) replaces. Inputs are not
|
|
156
|
-
* mutated.
|
|
157
|
-
*/
|
|
158
|
-
function deepMerge(base, overlay) {
|
|
159
|
-
if (overlay === undefined) return base;
|
|
160
|
-
if (!isPlainObject(base) || !isPlainObject(overlay)) {
|
|
161
|
-
return overlay;
|
|
162
|
-
}
|
|
163
|
-
const out = { ...base };
|
|
164
|
-
for (const [k, v] of Object.entries(overlay)) {
|
|
165
|
-
out[k] = k in base ? deepMerge(base[k], v) : v;
|
|
166
|
-
}
|
|
167
|
-
return out;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function isPlainObject(v) {
|
|
171
|
-
return (
|
|
172
|
-
v !== null &&
|
|
173
|
-
typeof v === "object" &&
|
|
174
|
-
!Array.isArray(v) &&
|
|
175
|
-
Object.getPrototypeOf(v) === Object.prototype
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
106
|
/**
|
|
180
107
|
* Normalize a being's persisted `system.body` from a `sohl.body` block. The
|
|
181
108
|
* authoring frontmatter mirrors the schema field-for-field: `sohl.body` nests
|
|
@@ -223,275 +150,17 @@ function extractBodyAndMovement(fm) {
|
|
|
223
150
|
};
|
|
224
151
|
}
|
|
225
152
|
|
|
226
|
-
|
|
227
|
-
* The key one predefined item is held under, and every place that spells it.
|
|
228
|
-
*
|
|
229
|
-
* **The vocabulary is the document's, not the note's** — `subType` is the
|
|
230
|
-
* Foundry Item subtype the compiled document carries, which is the only thing a
|
|
231
|
-
* compiled pack (or an extracted dependency catalogue) records about what an
|
|
232
|
-
* item *is*. A being's frontmatter addresses the same item in the *note*
|
|
233
|
-
* vocabulary, so a reference is translated forward through the system's map
|
|
234
|
-
* before it reaches this function; see {@link Actors#embeddedSubtype} for why
|
|
235
|
-
* the translation goes that way and not the other (#140).
|
|
236
|
-
*
|
|
237
|
-
* @param {string} subType - The Foundry Item subtype.
|
|
238
|
-
* @param {string} shortcode - The item's `system.shortcode`.
|
|
239
|
-
* @returns {string} The address, `subType:shortcode`.
|
|
240
|
-
*/
|
|
241
|
-
function itemAddress(subType, shortcode) {
|
|
242
|
-
return `${subType}:${shortcode}`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Load every JSON file under each of `itemsSourceDirs`, returning one Map keyed
|
|
247
|
-
* by {@link itemAddress} — the compiled document's **subtype** and its
|
|
248
|
-
* `system.shortcode`. Folder docs and entries without a shortcode are skipped.
|
|
249
|
-
* The `_key` field is stripped from each entry — it is not part of the item
|
|
250
|
-
* data model.
|
|
251
|
-
*
|
|
252
|
-
* The directories are read as one address space, because a being names an item
|
|
253
|
-
* by `(type, shortcode)` and never by the pack it happens to ship in. Two local
|
|
254
|
-
* Item packs claiming the same address is therefore ambiguous rather than a
|
|
255
|
-
* last-one-wins ordering detail, and fails here.
|
|
256
|
-
*
|
|
257
|
-
* A **foreign** directory — the extracted item catalogue of a package this
|
|
258
|
-
* repository depends on but does not contain — is a fallback rather than a
|
|
259
|
-
* peer. A repository must be able to ship its own `skill:awar` that stands in
|
|
260
|
-
* front of the system's, so a local address shadows a foreign one instead of
|
|
261
|
-
* colliding with it. Local directories are therefore read first, and anything
|
|
262
|
-
* already claimed is left alone.
|
|
263
|
-
*
|
|
264
|
-
* @param {readonly string[]} itemsSourceDirs - Every local Item pack's JSON tree.
|
|
265
|
-
* @param {readonly string[]} [foreignSourceDirs] - Extracted dependency
|
|
266
|
-
* catalogues, consulted only for addresses no local pack defines.
|
|
267
|
-
* @returns {Map<string, object>} The predefined items, by address.
|
|
268
|
-
*/
|
|
269
|
-
function loadItemsMap(itemsSourceDirs, foreignSourceDirs = []) {
|
|
270
|
-
const map = new Map();
|
|
271
|
-
const source = new Map();
|
|
272
|
-
const shadowed = [];
|
|
273
|
-
for (const itemsSourceDir of itemsSourceDirs) {
|
|
274
|
-
if (!fs.existsSync(itemsSourceDir)) {
|
|
275
|
-
// The generator orders the actors pass after every Item pass (#73),
|
|
276
|
-
// so a whole-package build cannot reach this. What can is a run
|
|
277
|
-
// restricted to this one pack, or a caller constructing the
|
|
278
|
-
// compiler itself — neither of which reordering a pack list fixes,
|
|
279
|
-
// so the message no longer suggests it.
|
|
280
|
-
throw new Error(
|
|
281
|
-
`Items source directory ${itemsSourceDir} does not exist — ` +
|
|
282
|
-
`a being resolves its embedded items against the Item ` +
|
|
283
|
-
`packs' compiled output, so those packs must be compiled ` +
|
|
284
|
-
`before this one`,
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
for (const name of fs.readdirSync(itemsSourceDir)) {
|
|
288
|
-
if (!name.endsWith(".json")) continue;
|
|
289
|
-
if (name.startsWith("folder_")) continue;
|
|
290
|
-
const full = path.join(itemsSourceDir, name);
|
|
291
|
-
let doc;
|
|
292
|
-
try {
|
|
293
|
-
doc = JSON.parse(fs.readFileSync(full, "utf8"));
|
|
294
|
-
} catch (err) {
|
|
295
|
-
emitDiagnostic({
|
|
296
|
-
file: full,
|
|
297
|
-
severity: "warning",
|
|
298
|
-
message: `unparseable item JSON, skipping: ${err.message}`,
|
|
299
|
-
});
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
const shortcode = doc?.system?.shortcode;
|
|
303
|
-
if (!doc?.type || !shortcode) continue;
|
|
304
|
-
const address = itemAddress(doc.type, shortcode);
|
|
305
|
-
const owner = source.get(address);
|
|
306
|
-
if (owner && owner !== itemsSourceDir) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
`Two Item packs both define "${address}" (${owner} and ` +
|
|
309
|
-
`${itemsSourceDir}); a being addresses an item by ` +
|
|
310
|
-
`(type, shortcode), so the address must be unique across ` +
|
|
311
|
-
`every Item pack`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
source.set(address, itemsSourceDir);
|
|
315
|
-
// eslint-disable-next-line no-unused-vars
|
|
316
|
-
const { _key, ...rest } = doc;
|
|
317
|
-
map.set(address, rest);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
for (const foreignDir of foreignSourceDirs) {
|
|
321
|
-
for (const name of fs.readdirSync(foreignDir)) {
|
|
322
|
-
if (!name.endsWith(".json")) continue;
|
|
323
|
-
if (name.startsWith("folder_")) continue;
|
|
324
|
-
const full = path.join(foreignDir, name);
|
|
325
|
-
let doc;
|
|
326
|
-
try {
|
|
327
|
-
doc = JSON.parse(fs.readFileSync(full, "utf8"));
|
|
328
|
-
} catch (err) {
|
|
329
|
-
emitDiagnostic({
|
|
330
|
-
file: full,
|
|
331
|
-
severity: "warning",
|
|
332
|
-
message: `unparseable item JSON, skipping: ${err.message}`,
|
|
333
|
-
});
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
const shortcode = doc?.system?.shortcode;
|
|
337
|
-
if (!doc?.type || !shortcode) continue;
|
|
338
|
-
const address = itemAddress(doc.type, shortcode);
|
|
339
|
-
if (map.has(address)) {
|
|
340
|
-
// Deliberate: this repository defines it, so its version wins.
|
|
341
|
-
if (source.has(address)) shadowed.push(address);
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
// eslint-disable-next-line no-unused-vars
|
|
345
|
-
const { _key, ...rest } = doc;
|
|
346
|
-
map.set(address, rest);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (shadowed.length) {
|
|
350
|
-
log.info(
|
|
351
|
-
`${shadowed.length} dependency item(s) shadowed by this ` +
|
|
352
|
-
`repository's own: ${shadowed.slice(0, 5).join(", ")}` +
|
|
353
|
-
(shadowed.length > 5 ? ", …" : ""),
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
return map;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Extract the body of an H1 section whose heading carries the explicit
|
|
361
|
-
* anchor decorator `{#<anchorId>}`. Captures every line after the H1 up
|
|
362
|
-
* to (but not including) the next H1 — nested H2/H3 etc. and their bodies
|
|
363
|
-
* are included. The H1 line itself is discarded. Returns "" if no such
|
|
364
|
-
* heading exists. Fenced code blocks are respected so `# foo` inside
|
|
365
|
-
* ``` blocks does not trigger a match.
|
|
366
|
-
*/
|
|
367
|
-
function extractAnchorSection(body, anchorId) {
|
|
368
|
-
const lines = body.split("\n");
|
|
369
|
-
const captured = [];
|
|
370
|
-
let inCodeBlock = false;
|
|
371
|
-
let capturing = false;
|
|
372
|
-
const wanted = String(anchorId).toLowerCase();
|
|
373
|
-
for (const line of lines) {
|
|
374
|
-
if (line.trim().startsWith("```")) {
|
|
375
|
-
inCodeBlock = !inCodeBlock;
|
|
376
|
-
if (capturing) captured.push(line);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
const h1Match = !inCodeBlock ? line.match(/^\s*#\s+(.+?)\s*#*\s*$/) : null;
|
|
380
|
-
if (h1Match) {
|
|
381
|
-
const anchor = h1Match[1].match(/\{#([^}]+)\}\s*$/);
|
|
382
|
-
const id = anchor?.[1]?.trim().toLowerCase() || null;
|
|
383
|
-
if (capturing) break;
|
|
384
|
-
if (id === wanted) {
|
|
385
|
-
capturing = true;
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (capturing) captured.push(line);
|
|
390
|
-
}
|
|
391
|
-
return captured.join("\n").trim();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Render an extracted markdown section to HTML, or "" if empty.
|
|
396
|
-
*/
|
|
397
|
-
function renderSection(body, anchorId) {
|
|
398
|
-
const slice = extractAnchorSection(body, anchorId);
|
|
399
|
-
return slice ? md.render(slice) : "";
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export class Actors extends BasePackCompiler {
|
|
403
|
-
static id = "actors";
|
|
404
|
-
static label = "actor";
|
|
405
|
-
|
|
406
|
-
// A being's embedded items are resolved against the *output* of the item
|
|
407
|
-
// passes, so every Item pack compiles before this one. Declared rather than
|
|
408
|
-
// left to the order `packs:` happens to list (#73).
|
|
409
|
-
static readsPackOutputOf = Object.freeze(["Item"]);
|
|
410
|
-
|
|
411
|
-
/** @type {readonly string[]} */
|
|
412
|
-
itemsSourceDirs;
|
|
413
|
-
foreignSourceDirs;
|
|
414
|
-
|
|
415
|
-
constructor({ itemsSourceDirs = [], foreignSourceDirs = [], ...options }) {
|
|
416
|
-
super(options);
|
|
417
|
-
// Where the items passes wrote their JSON. Stated by the caller rather
|
|
418
|
-
// than assumed to be this pack's sibling: the packs' locations are
|
|
419
|
-
// configuration, and a consumer may put them anywhere (#1508). Every
|
|
420
|
-
// Item pack, because a repository may ship more than one (#1566).
|
|
421
|
-
//
|
|
422
|
-
// **Optional, and empty is a legitimate package (#49).** This used to
|
|
423
|
-
// throw unless at least one Item pack was declared, which asked a
|
|
424
|
-
// package to declare the very thing it may exist not to have. An Item
|
|
425
|
-
// pack is system-bound by construction — Foundry requires `system` on
|
|
426
|
-
// Item packs — so a deliberately system-agnostic module could satisfy
|
|
427
|
-
// the guard only by naming a system. `harn-ensemble` is the case:
|
|
428
|
-
// 2,512 beings whose embedded items address the `sohl` and `hm3`
|
|
429
|
-
// catalogues, and five affiliation notes of its own.
|
|
430
|
-
//
|
|
431
|
-
// The guard also did not test what it claimed. It counted *declared
|
|
432
|
-
// directories*, not resolvable items, so an empty Item pack satisfied
|
|
433
|
-
// it while a being naming a missing item still failed later. The
|
|
434
|
-
// condition actually cared about is checked where it can be reported
|
|
435
|
-
// precisely: {@link Actors#resolveEmbedded} already errors per
|
|
436
|
-
// unresolved `(type, shortcode)`, naming the being. A package whose
|
|
437
|
-
// beings embed nothing, or whose every address resolves against a
|
|
438
|
-
// dependency catalogue through `foreignSourceDirs`, now compiles with
|
|
439
|
-
// no Item pack at all — and one that is genuinely missing an item
|
|
440
|
-
// still fails, saying which item and which actor rather than which
|
|
441
|
-
// pack is absent.
|
|
442
|
-
Object.defineProperty(this, "itemsSourceDirs", {
|
|
443
|
-
value: Object.freeze([...itemsSourceDirs]),
|
|
444
|
-
writable: false,
|
|
445
|
-
});
|
|
446
|
-
// The dependency catalogues, if any. Not required: a repository that
|
|
447
|
-
// holds every item its beings name needs none, and one that declares
|
|
448
|
-
// no `itemCatalog: true` relationship gets an empty list.
|
|
449
|
-
Object.defineProperty(this, "foreignSourceDirs", {
|
|
450
|
-
value: Object.freeze([...foreignSourceDirs]),
|
|
451
|
-
writable: false,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
153
|
+
export class Actors extends SystemActorCompiler {
|
|
455
154
|
/**
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* embedded item reference's — reads one declaration that a subclass
|
|
461
|
-
* compiling for another system can replace. That is also what lets the
|
|
462
|
-
* non-identity behaviour be exercised without introducing a non-identity
|
|
463
|
-
* row into SoHL's own map, which is #78's job and moves compiled bytes.
|
|
155
|
+
* SoHL's note-type → document-subtype map — the one declaration that says
|
|
156
|
+
* which block this pass reads, which notes it claims, and what each
|
|
157
|
+
* becomes (#79). It is also what every embedded reference is translated
|
|
158
|
+
* through, which is why a subclass replaces one thing and not two.
|
|
464
159
|
*
|
|
465
160
|
* @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
|
|
466
161
|
*/
|
|
467
162
|
static documentSubtypes = SOHL_DOCUMENT_SUBTYPES;
|
|
468
163
|
|
|
469
|
-
/**
|
|
470
|
-
* @param {object} fm - The note's frontmatter.
|
|
471
|
-
* @returns {boolean} True for a note type the system maps onto an `Actor`.
|
|
472
|
-
*/
|
|
473
|
-
selects(fm) {
|
|
474
|
-
return mapsNoteType(this.constructor.documentSubtypes, fm.type, "Actor");
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* An Actor **is** a system's data, so this pack takes only notes carrying
|
|
479
|
-
* this system's block (#58).
|
|
480
|
-
*/
|
|
481
|
-
static requiresSystemBlock = true;
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* The predefined items each being's embedded items resolve against, loaded
|
|
485
|
-
* before the walk from the items passes' output.
|
|
486
|
-
*
|
|
487
|
-
* @returns {Promise<void>}
|
|
488
|
-
*/
|
|
489
|
-
async prepare() {
|
|
490
|
-
await super.prepare();
|
|
491
|
-
this.itemsMap = loadItemsMap(this.itemsSourceDirs, this.foreignSourceDirs);
|
|
492
|
-
log.info(`Loaded ${this.itemsMap.size} predefined items for actor resolution`);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
164
|
/**
|
|
496
165
|
* Compile one `being` note into its actor document.
|
|
497
166
|
*
|
|
@@ -504,132 +173,6 @@ export class Actors extends BasePackCompiler {
|
|
|
504
173
|
return this.buildBeing(this.itemsMap, fm, markdown);
|
|
505
174
|
}
|
|
506
175
|
|
|
507
|
-
/** @inheritdoc */
|
|
508
|
-
reportDetail(stats) {
|
|
509
|
-
log.debug(
|
|
510
|
-
`Skipped ${stats.skippedOther} non-actor file(s) ` +
|
|
511
|
-
`(not ${ACTOR_NOTE_TYPES.join("/")}, package:${contentPackage()})`,
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* The Foundry Item subtype an embedded reference's `type` addresses.
|
|
517
|
-
*
|
|
518
|
-
* **The reference is in the note vocabulary; the address is in the
|
|
519
|
-
* document's** (#140). A being writes `(type, shortcode)` with the type an
|
|
520
|
-
* author authors, while {@link itemAddress} keys the predefined items by
|
|
521
|
-
* the subtype each compiled document carries — so exactly one of the two
|
|
522
|
-
* sides has to translate, and it is this one. The system's map is a
|
|
523
|
-
* function from note type to subtype by construction; the reverse is not,
|
|
524
|
-
* and a compiled document records nothing about the note that produced it,
|
|
525
|
-
* so there is no honest way to key the addresses the other way round.
|
|
526
|
-
*
|
|
527
|
-
* The two vocabularies are the same string in every SoHL row today, which
|
|
528
|
-
* is why looking a reference up verbatim worked. The first non-identity row
|
|
529
|
-
* (#78: `armor` → `armorgear`) ends that, and a reference resolving to
|
|
530
|
-
* nothing must be a finding rather than an item quietly missing from the
|
|
531
|
-
* compiled actor.
|
|
532
|
-
*
|
|
533
|
-
* @param {string} type - The type the reference names.
|
|
534
|
-
* @returns {import("../engine/document-subtypes.mjs").ReferencedSubtype}
|
|
535
|
-
* The subtype, or why the reference names none.
|
|
536
|
-
*/
|
|
537
|
-
embeddedSubtype(type) {
|
|
538
|
-
return referencedSubtype(this.constructor.documentSubtypes, type, "Item");
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Resolve one embedded item from a `(type, shortcode?, overlay)`
|
|
543
|
-
* descriptor. If `shortcode` is given, the predefined item is fetched
|
|
544
|
-
* from `itemsMap` and the overlay deep-merged on top. If absent, the
|
|
545
|
-
* descriptor must carry enough fields to stand alone. The embedded
|
|
546
|
-
* item's `_id` is regenerated deterministically from
|
|
547
|
-
* `(actorId, subType, shortcode, indexKey)` so re-exports are stable —
|
|
548
|
-
* from the **document subtype**, so that renaming a note type (#78) leaves
|
|
549
|
-
* every embedded id exactly where it was.
|
|
550
|
-
* Returns null if the descriptor cannot be resolved.
|
|
551
|
-
*
|
|
552
|
-
* @param {Map<string, object>} itemsMap - The predefined items, by address.
|
|
553
|
-
* @param {string} actorId - The owning actor's id, seeding embedded ids.
|
|
554
|
-
* @param {string} type - The **note** type the reference names.
|
|
555
|
-
* @param {string|null} shortcode - The referenced item's shortcode, or
|
|
556
|
-
* `null` for a stand-alone entry.
|
|
557
|
-
* @param {object} [overlay] - The entry's remaining properties.
|
|
558
|
-
* @param {string} indexKey - Distinguishes two references to one item.
|
|
559
|
-
* @param {string} ctx - Diagnostic context (the actor's label).
|
|
560
|
-
* @param {object} [at] - Where to locate a finding.
|
|
561
|
-
* @param {string} [at.fmKey] - The frontmatter key the reference sits
|
|
562
|
-
* under, so an unresolved one is reported at the reference rather than
|
|
563
|
-
* at the note.
|
|
564
|
-
* @returns {object|null} The embedded item, or null when it resolved to
|
|
565
|
-
* nothing — always with a finding emitted.
|
|
566
|
-
*/
|
|
567
|
-
resolveEmbedded(itemsMap, actorId, type, shortcode, overlay, indexKey, ctx, { fmKey } = {}) {
|
|
568
|
-
// Where a finding about this reference points. The value locates the
|
|
569
|
-
// exact entry in a list; the key is the fallback when it cannot be
|
|
570
|
-
// found, which still beats naming the note alone.
|
|
571
|
-
const where = () =>
|
|
572
|
-
locateFrontmatterKey(this.currentNote?.absPath, fmKey ?? "items", shortcode || type);
|
|
573
|
-
|
|
574
|
-
const { subType, problem } = this.embeddedSubtype(type);
|
|
575
|
-
if (problem) {
|
|
576
|
-
this.noteError(`${ctx}: ${indexKey}: ${problem}`, where());
|
|
577
|
-
this.errorCount++;
|
|
578
|
-
return null;
|
|
579
|
-
}
|
|
580
|
-
const address = itemAddress(/** @type {string} */ (subType), shortcode ?? "");
|
|
581
|
-
|
|
582
|
-
let base = null;
|
|
583
|
-
if (shortcode) {
|
|
584
|
-
base = itemsMap.get(address);
|
|
585
|
-
if (!base) {
|
|
586
|
-
// Both vocabularies where they differ, so an author sees why an
|
|
587
|
-
// address they wrote did not land where they expected.
|
|
588
|
-
const translated =
|
|
589
|
-
subType === type ? "" : (
|
|
590
|
-
` (looked up as "${address}", the ` +
|
|
591
|
-
`${this.constructor.documentSubtypes.system} Item subtype a ` +
|
|
592
|
-
`"${type}" note compiles into)`
|
|
593
|
-
);
|
|
594
|
-
this.noteError(
|
|
595
|
-
`${ctx}: no predefined item for "${type}:${shortcode}"${translated}`,
|
|
596
|
-
where(),
|
|
597
|
-
);
|
|
598
|
-
this.errorCount++;
|
|
599
|
-
return null;
|
|
600
|
-
}
|
|
601
|
-
base = stripCompendiumFields(base);
|
|
602
|
-
} else if (overlay && overlay.name && overlay.system) {
|
|
603
|
-
base = { type: subType, name: overlay.name, system: {} };
|
|
604
|
-
} else {
|
|
605
|
-
this.noteError(
|
|
606
|
-
`${ctx}: embedded item missing shortcode and not enough fields to stand alone`,
|
|
607
|
-
where(),
|
|
608
|
-
);
|
|
609
|
-
this.errorCount++;
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
const merged = overlay ? deepMerge(base, overlay) : base;
|
|
613
|
-
merged.type = subType;
|
|
614
|
-
merged._id = makeId(
|
|
615
|
-
actorId,
|
|
616
|
-
`${itemAddress(/** @type {string} */ (subType), shortcode || merged.name)}:${indexKey}`,
|
|
617
|
-
);
|
|
618
|
-
// Foundry's pack compiler flattens the document hierarchy into LevelDB,
|
|
619
|
-
// storing each embedded document under its own `_key`. Embedded items
|
|
620
|
-
// therefore need a hierarchical key, as do any effects they carry
|
|
621
|
-
// (re-keyed under this actor's item rather than the items-pack key they
|
|
622
|
-
// inherited). Mirrors the items pack convention in items.mjs.
|
|
623
|
-
merged._key = `!actors.items!${actorId}.${merged._id}`;
|
|
624
|
-
if (Array.isArray(merged.effects)) {
|
|
625
|
-
for (const effect of merged.effects) {
|
|
626
|
-
if (!effect?._id) continue;
|
|
627
|
-
effect._key = `!actors.items.effects!${actorId}.${merged._id}.${effect._id}`;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
return merged;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
176
|
/**
|
|
634
177
|
* Build all embedded items for an actor: one per `sohl.attributes`
|
|
635
178
|
* entry plus one per `sohl.items` entry. `sohl.skills` is ignored.
|
|
@@ -752,8 +295,8 @@ export class Actors extends BasePackCompiler {
|
|
|
752
295
|
|
|
753
296
|
const items = this.buildEmbeddedItems(itemsMap, id, fm, ctx);
|
|
754
297
|
|
|
755
|
-
const
|
|
756
|
-
const folder = this.folderResolver(
|
|
298
|
+
const { value: authoredFolder, isAddress } = folderField(fm);
|
|
299
|
+
const folder = this.folderResolver(authoredFolder, { isAddress });
|
|
757
300
|
|
|
758
301
|
const system = {
|
|
759
302
|
// The frontmatter shortcode is the actor's stable `(type, shortcode)`
|
|
@@ -761,8 +304,11 @@ export class Actors extends BasePackCompiler {
|
|
|
761
304
|
// identity (the dedup/override key of the Create-dialog picker, #604).
|
|
762
305
|
shortcode: fm.shortcode || "",
|
|
763
306
|
// Required nullable number: a priority, or `null` for a being that
|
|
764
|
-
// is not
|
|
765
|
-
archetype
|
|
307
|
+
// is not a template (#126 / archetype contract #604). The field was
|
|
308
|
+
// `system.archetype` until #266 / sohl#1836; the emitted key moves
|
|
309
|
+
// with the schema, since an undeclared `system` key is discarded at
|
|
310
|
+
// construction without a warning.
|
|
311
|
+
templatePriority: systemTemplatePriority(fm, ctx),
|
|
766
312
|
// Nullish, not `||` (#218): a note that names no portrait gets the
|
|
767
313
|
// subtype's default, one that writes `""` ships blank on purpose.
|
|
768
314
|
portrait: resolveImg(blockProperty(fm, SYSTEM, "portrait")) ?? defaultImg,
|
|
@@ -803,7 +349,7 @@ export class Actors extends BasePackCompiler {
|
|
|
803
349
|
this.reportUndeclaredSystemData(fm, SYSTEM, "Actor", subType);
|
|
804
350
|
// And what this pass wrote itself. There is no field declaration for a
|
|
805
351
|
// being at all, so *every* key here is a compiler emission — including
|
|
806
|
-
// `
|
|
352
|
+
// `templatePriority` (#126), which nothing compared until #155.
|
|
807
353
|
this.reportEmittedSystemData(system, {
|
|
808
354
|
fm,
|
|
809
355
|
block: SYSTEM,
|
|
@@ -836,7 +382,7 @@ export class Actors extends BasePackCompiler {
|
|
|
836
382
|
folder,
|
|
837
383
|
sort: 0,
|
|
838
384
|
ownership: { default: 0 },
|
|
839
|
-
// Whatever the note authors, and nothing else.
|
|
385
|
+
// Whatever the note authors, and nothing else. The priority used to
|
|
840
386
|
// be spliced in here as `flags.sohl.docArchetype`; it is a schema
|
|
841
387
|
// field now and sits in `system` (#126).
|
|
842
388
|
flags: blockProperty(fm, SYSTEM, "flags", {}),
|
package/sohl/being-info.mjs
CHANGED
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
* @module
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
+
// The retirement window for a renamed note type (#78): an embedded reference
|
|
40
|
+
// still spelling `armorgear` names the same gear group as `armor`.
|
|
41
|
+
import { currentType } from "../engine/ids.mjs";
|
|
42
|
+
|
|
39
43
|
/**
|
|
40
44
|
* The note `type` whose pages carry a being info block.
|
|
41
45
|
*
|
|
@@ -59,19 +63,24 @@ export function isBeing(fm) {
|
|
|
59
63
|
/**
|
|
60
64
|
* The sidebar group each gear item type is displayed under.
|
|
61
65
|
*
|
|
62
|
-
* Presentation naming, not data-model naming: the
|
|
63
|
-
* sidebar heading says "weapons". Kept as one table so a new gear type is
|
|
64
|
-
* in a single place rather than in each consumer's site build.
|
|
66
|
+
* Presentation naming, not data-model naming: the note type says `weapongear`,
|
|
67
|
+
* the sidebar heading says "weapons". Kept as one table so a new gear type is
|
|
68
|
+
* added in a single place rather than in each consumer's site build.
|
|
69
|
+
*
|
|
70
|
+
* Keyed by **note** type, which is what a being's embedded `(type, shortcode)`
|
|
71
|
+
* references spell — and since #78 three of those are no longer the document
|
|
72
|
+
* subtype they compile into. A reference still on a renamed spelling is
|
|
73
|
+
* normalised at the lookup below rather than given a second row here.
|
|
65
74
|
*
|
|
66
75
|
* @type {Readonly<Record<string, string>>}
|
|
67
76
|
*/
|
|
68
77
|
export const GEAR_TYPE_TO_KEY = Object.freeze({
|
|
69
78
|
weapongear: "weapons",
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
armor: "armor",
|
|
80
|
+
projectile: "projectiles",
|
|
72
81
|
miscgear: "misc",
|
|
73
82
|
containergear: "containers",
|
|
74
|
-
|
|
83
|
+
concoction: "concoctions",
|
|
75
84
|
});
|
|
76
85
|
|
|
77
86
|
/** Whether a value is a plain mapping. */
|
|
@@ -132,7 +141,7 @@ export function deriveBeingInfo(sohl, index) {
|
|
|
132
141
|
const gear = {};
|
|
133
142
|
for (const it of items) {
|
|
134
143
|
if (!isMap(it)) continue;
|
|
135
|
-
const key = GEAR_TYPE_TO_KEY[it.type];
|
|
144
|
+
const key = GEAR_TYPE_TO_KEY[currentType(it.type)];
|
|
136
145
|
if (!key) continue;
|
|
137
146
|
const shortcode = typeof it.shortcode === "string" ? it.shortcode : undefined;
|
|
138
147
|
const ref = lookup(it.type, shortcode);
|