@heroiclands/package-build 17.2.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 +1711 -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 +225 -127
- 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} +55 -83
- 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/manifest.mjs +43 -2
- package/package.json +18 -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 +54 -49
- 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} +2 -43
- 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
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
* **HM3's item-type registry** — every content type that compiles into an HM3
|
|
16
|
+
* Foundry Item, keyed to the builder that produces its `system` block.
|
|
17
|
+
*
|
|
18
|
+
* The same arrangement as `sohl/item-builders.mjs`, and a repository feeding
|
|
19
|
+
* both systems names both: `itemBuilders: [sohl, hm3]`. The engine keeps them
|
|
20
|
+
* apart from there on — `itemBuilder(type, system)` takes the system that is
|
|
21
|
+
* asking, and a type both registries declare **throws** rather than resolving
|
|
22
|
+
* when nobody says which (#58). That guard existed before there was a second
|
|
23
|
+
* registry to trip it; this is the registry it was written for.
|
|
24
|
+
*
|
|
25
|
+
* **The builders are generated, not written**, from the field declarations in
|
|
26
|
+
* `item-fields.mjs`, compiled against the `hm3` block. `buildFromFields` takes
|
|
27
|
+
* the block as an argument precisely so one declaration mechanism serves any
|
|
28
|
+
* number of systems: `data.weight` reaches `system.weightBase` under `sohl:`
|
|
29
|
+
* and `system.weight` under `hm3:`, from two declarations and one engine.
|
|
30
|
+
*
|
|
31
|
+
* **No finalizers.** SoHL has one — a combat technique's strike mode, whose
|
|
32
|
+
* *presence* depends on another field's value — and HM3 has no such
|
|
33
|
+
* conditional. The absence is worth stating: a finalizer is opaque to every
|
|
34
|
+
* reader a declaration is legible to, so having none is the state to keep.
|
|
35
|
+
*
|
|
36
|
+
* **A leaf.** It imports the declaration primitives, the field table and the
|
|
37
|
+
* art map, and never the resolved configuration — the configuration file
|
|
38
|
+
* imports *this*, so a read back out would close a cycle around the
|
|
39
|
+
* configuration's own evaluation.
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { buildFromFields } from "../engine/field-spec.mjs";
|
|
45
|
+
import { hm3DefaultItemArt } from "./default-item-art.mjs";
|
|
46
|
+
import { HM3_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
47
|
+
import { HM3_ITEM_FIELDS } from "./item-fields.mjs";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The frontmatter block these builders read.
|
|
51
|
+
*
|
|
52
|
+
* Taken from the map rather than spelled here, so the block name, the subtype
|
|
53
|
+
* map and the registry are one statement.
|
|
54
|
+
*
|
|
55
|
+
* @type {string}
|
|
56
|
+
*/
|
|
57
|
+
const BLOCK = HM3_DOCUMENT_SUBTYPES.block;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build one registry entry: the declared fields, the builder they generate, and
|
|
61
|
+
* the default art for the type.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} type - The note type, and the art map's key.
|
|
64
|
+
* @returns {Readonly<{system: (fm: object) => object, img: string, fields: readonly object[]}>}
|
|
65
|
+
* The registry entry.
|
|
66
|
+
*/
|
|
67
|
+
function entryFor(type) {
|
|
68
|
+
const fields = HM3_ITEM_FIELDS[type];
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
system: buildFromFields(fields, { block: BLOCK }),
|
|
71
|
+
img: hm3DefaultItemArt(type),
|
|
72
|
+
fields,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Every HM3 item type, paired with the builder for its `system` block, the
|
|
78
|
+
* default art for the type, and the frontmatter fields it declares.
|
|
79
|
+
*
|
|
80
|
+
* @type {Readonly<Record<string, Readonly<{system: (fm: object) => object, img: string, fields: readonly object[]}>>>}
|
|
81
|
+
*/
|
|
82
|
+
export const HM3_ITEM_BUILDERS = Object.freeze(
|
|
83
|
+
Object.fromEntries(Object.keys(HM3_ITEM_FIELDS).map((type) => [type, entryFor(type)])),
|
|
84
|
+
);
|
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
* The `hm3:` frontmatter vocabulary of every HM3 item type.
|
|
16
|
+
*
|
|
17
|
+
* The same arrangement `sohl/item-fields.mjs` describes: the declaration **is**
|
|
18
|
+
* the builder — {@link buildFromFields} turns each list into the function that
|
|
19
|
+
* runs — so a field that is not here is not emitted, and there is no second
|
|
20
|
+
* statement of the mapping to drift.
|
|
21
|
+
*
|
|
22
|
+
* **Deliberately shorter than SoHL's, and that is the honest answer.** The
|
|
23
|
+
* mapping tables in `docs/content-format.md` are the specification for what a
|
|
24
|
+
* shared `data:` fact becomes in each system, and HM3's column is `NA` far more
|
|
25
|
+
* often than SoHL's: a weapon's weight and value cross over, its quality and
|
|
26
|
+
* durability do not. Every row below is one the specification states. Where it
|
|
27
|
+
* says nothing — HM3's `armorlocation` has no `data:` row at all, and `trauma`
|
|
28
|
+
* and `mysticalability` have none either — this file declares nothing rather
|
|
29
|
+
* than inventing a plausible path. A guessed `to` compiles clean and is
|
|
30
|
+
* discarded by Foundry at load without a word, which is exactly the failure the
|
|
31
|
+
* schema check exists to catch and exactly the failure a guess would create.
|
|
32
|
+
*
|
|
33
|
+
* **One shared source, two destinations.** `name` is the shared property a
|
|
34
|
+
* field draws from and `to` is where it lands, so a single authored `weight`
|
|
35
|
+
* feeds `sohl.system.weightBase` *and* `hm3.system.weight` — one authored fact,
|
|
36
|
+
* two documents (#58). That is the whole reason the two are declared
|
|
37
|
+
* separately.
|
|
38
|
+
*
|
|
39
|
+
* The specification writes those sources as `data.weight`, and the sources here
|
|
40
|
+
* are written bare, exactly as SoHL's are: the `data:` region is #128's
|
|
41
|
+
* migration and neither half has moved to it. Spelling HM3's differently would
|
|
42
|
+
* make a note feeding both systems author the same fact twice for no gain, and
|
|
43
|
+
* would take these rows out of reach of `content-build content-format fields`,
|
|
44
|
+
* which pairs a `data.<key>` claim with a bare declared `<key>`. Both halves
|
|
45
|
+
* move together when #128 lands.
|
|
46
|
+
*
|
|
47
|
+
* **What a note authors under `hm3.system` is not declared here.** HM3 fields
|
|
48
|
+
* that no shared `data:` property feeds — a skill's `type`, an armour's
|
|
49
|
+
* `protection`, a character's `abilities` — are written at their own paths in
|
|
50
|
+
* the note's `hm3.system` block and reach the document through the verbatim
|
|
51
|
+
* passthrough. They are checked against HM3's published `schema.json` like
|
|
52
|
+
* everything else; they simply have no shared source to declare.
|
|
53
|
+
*
|
|
54
|
+
* **A leaf**, like its SoHL counterpart: declaration primitives only, never the
|
|
55
|
+
* resolved configuration.
|
|
56
|
+
*
|
|
57
|
+
* @module
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
import { AS_AUTHORED, NUMBER } from "../engine/field-spec.mjs";
|
|
61
|
+
|
|
62
|
+
/* --------------------------------------------------------------------- */
|
|
63
|
+
/* Rows shared by the four gear types */
|
|
64
|
+
/* --------------------------------------------------------------------- */
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* What HM3's gear template takes from the shared `data:` region.
|
|
68
|
+
*
|
|
69
|
+
* `weight` and `value` are the two rows every gear table in the content format
|
|
70
|
+
* gives an HM3 destination. `quality` and `durability` are SoHL's alone — HM3
|
|
71
|
+
* carries `weaponQuality` and `armorQuality` on the subtypes that have them,
|
|
72
|
+
* which is a different fact about a different thing, so the shared row is `NA`
|
|
73
|
+
* and nothing is emitted.
|
|
74
|
+
*
|
|
75
|
+
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
76
|
+
*/
|
|
77
|
+
const GEAR_COMMON = Object.freeze([
|
|
78
|
+
{
|
|
79
|
+
name: "weight",
|
|
80
|
+
to: "weight",
|
|
81
|
+
...AS_AUTHORED,
|
|
82
|
+
kind: "number",
|
|
83
|
+
default: 0,
|
|
84
|
+
describe: "Weight of one, in pounds.",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "value",
|
|
88
|
+
to: "value",
|
|
89
|
+
...AS_AUTHORED,
|
|
90
|
+
kind: "number",
|
|
91
|
+
default: 0,
|
|
92
|
+
describe: "Worth of one, in pence.",
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The stack count, for the gear types whose content-format table gives it an
|
|
98
|
+
* HM3 destination.
|
|
99
|
+
*
|
|
100
|
+
* `armor`, `containergear` and `weapongear` are excluded on purpose: their
|
|
101
|
+
* tables state that quantity is always 1 and may not be authored, so the note
|
|
102
|
+
* has nothing to say and HM3's own `initial` is the right value to ship.
|
|
103
|
+
*
|
|
104
|
+
* @type {import("../engine/field-spec.mjs").FieldSpec}
|
|
105
|
+
*/
|
|
106
|
+
const QUANTITY = Object.freeze({
|
|
107
|
+
name: "quantity",
|
|
108
|
+
to: "quantity",
|
|
109
|
+
...NUMBER,
|
|
110
|
+
default: 1,
|
|
111
|
+
describe: "How many the stack holds.",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/* --------------------------------------------------------------------- */
|
|
115
|
+
/* Per-type declarations */
|
|
116
|
+
/* --------------------------------------------------------------------- */
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Every HM3 item type's frontmatter vocabulary, in the order the `system` block
|
|
120
|
+
* emits it.
|
|
121
|
+
*
|
|
122
|
+
* The keys are **note** types, not HM3 document subtypes — `projectile`
|
|
123
|
+
* rather than `missilegear`, `mysticalability` rather than `psionic` — because
|
|
124
|
+
* a registry is addressed by what a note calls itself. What the document is
|
|
125
|
+
* called is the map's answer, and only the map's.
|
|
126
|
+
*
|
|
127
|
+
* @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
|
|
128
|
+
*/
|
|
129
|
+
export const HM3_ITEM_FIELDS = Object.freeze({
|
|
130
|
+
armor: Object.freeze([...GEAR_COMMON]),
|
|
131
|
+
|
|
132
|
+
// The one subtype that extends the Foundry base directly, with none of the
|
|
133
|
+
// shared templates. Its content-format table has a single row, and that row
|
|
134
|
+
// is `NA` on both sides — so an armour location is authored entirely under
|
|
135
|
+
// `hm3.system` (`probWeight`, `impactType`, the per-aspect protections) and
|
|
136
|
+
// there is nothing for a shared source to feed.
|
|
137
|
+
armorlocation: Object.freeze([]),
|
|
138
|
+
|
|
139
|
+
containergear: Object.freeze([
|
|
140
|
+
...GEAR_COMMON,
|
|
141
|
+
{
|
|
142
|
+
name: "capacity",
|
|
143
|
+
to: "capacity.max",
|
|
144
|
+
...NUMBER,
|
|
145
|
+
default: 0,
|
|
146
|
+
describe: "How much the container holds, in pounds.",
|
|
147
|
+
},
|
|
148
|
+
]),
|
|
149
|
+
|
|
150
|
+
miscgear: Object.freeze([...GEAR_COMMON, QUANTITY]),
|
|
151
|
+
|
|
152
|
+
// A note's `projectile` is HM3's `missilegear`; the fields are the gear
|
|
153
|
+
// template's, and the aspect, impact and range that make it a missile are
|
|
154
|
+
// authored under `hm3.system`.
|
|
155
|
+
projectile: Object.freeze([...GEAR_COMMON, QUANTITY]),
|
|
156
|
+
|
|
157
|
+
// `hm3.system.type` — "Craft", "Physical", "Communication", "Combat",
|
|
158
|
+
// "Magic", "Ritual" — has no shared source: the content format states
|
|
159
|
+
// outright that it does not map onto the note's `subType` and must be
|
|
160
|
+
// written for each HM3 skill. So it is authored at its own path and passes
|
|
161
|
+
// through, and only the mastery level is declared here.
|
|
162
|
+
skill: Object.freeze([
|
|
163
|
+
{
|
|
164
|
+
name: "masteryLevel",
|
|
165
|
+
to: "masteryLevel",
|
|
166
|
+
...NUMBER,
|
|
167
|
+
default: 0,
|
|
168
|
+
describe: "Mastery level, as a percentage.",
|
|
169
|
+
},
|
|
170
|
+
]),
|
|
171
|
+
|
|
172
|
+
// Both one-to-many item rows: every shared row in their content-format
|
|
173
|
+
// tables is `NA`, so what an HM3 trauma or mystical ability carries — a
|
|
174
|
+
// severity and heal rate, a convocation and level, a circle and deity — is
|
|
175
|
+
// authored under `hm3.system` against whichever subtype `hm3.type` names.
|
|
176
|
+
trauma: Object.freeze([]),
|
|
177
|
+
mysticalability: Object.freeze([]),
|
|
178
|
+
|
|
179
|
+
weapongear: Object.freeze([...GEAR_COMMON]),
|
|
180
|
+
});
|
package/hm3/items.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
* **HM3's Item pass** — the two things about compiling a note into an HM3 Item
|
|
16
|
+
* that are facts about HM3 rather than about the note format (#139).
|
|
17
|
+
*
|
|
18
|
+
* Everything else is {@link module:engine/item-compiler}'s, and is the same
|
|
19
|
+
* code the SoHL pass runs: which notes are claimed, which subtype each becomes,
|
|
20
|
+
* which registry builds it, the authored `hm3.system` passthrough, the schema
|
|
21
|
+
* check, and the compendium envelope. A second system is a map and a handful of
|
|
22
|
+
* emitted keys, which is the arrangement #79 and #58 were building towards.
|
|
23
|
+
*
|
|
24
|
+
* **What HM3's compiler writes on every item: one key, and only when there is
|
|
25
|
+
* something to write.** The content format gives an item's `{#appearance}`
|
|
26
|
+
* section a home in HM3 — `description` — and SoHL none, since no SoHL Item
|
|
27
|
+
* subtype declares such a field. So the section is rendered here and nowhere
|
|
28
|
+
* else. Where a note has no such section nothing is emitted at all, which
|
|
29
|
+
* matters for `armorlocation`: it is the one HM3 subtype that extends the
|
|
30
|
+
* Foundry base directly, declaring neither `description` nor `notes`, so an
|
|
31
|
+
* unconditional key would be a finding on every armour-location document in the
|
|
32
|
+
* pack.
|
|
33
|
+
*
|
|
34
|
+
* **And one flag, for the fact HM3's `system` has no field for.** The template
|
|
35
|
+
* priority is a shared statement — a note declaring `data.templatePriority` says
|
|
36
|
+
* the same thing to both systems — but HM3 declares no `system` field for it, so
|
|
37
|
+
* it lands under this system's own flag scope as `flags.hm3.templatePriority`,
|
|
38
|
+
* exactly as the Actor pass writes it. This pass wrote no such flag until #283:
|
|
39
|
+
* an item note declaring the priority compiled into a SoHL item that knew it was
|
|
40
|
+
* a template and an HM3 item that did not.
|
|
41
|
+
*
|
|
42
|
+
* **There is no HM3 equivalent of `docHtml`.** SoHL points an item at the
|
|
43
|
+
* JournalEntry its prose compiled into, and HM3's data model has nowhere to put
|
|
44
|
+
* such a pointer; inventing one would emit a key Foundry discards at load
|
|
45
|
+
* without a word. The prose still compiles into its JournalEntry — the journals
|
|
46
|
+
* pass claims every doc-carrying type regardless of system — it simply is not
|
|
47
|
+
* addressed from the item.
|
|
48
|
+
*
|
|
49
|
+
* @module
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import { renderSection } from "../engine/anchored-sections.mjs";
|
|
53
|
+
import { SystemItemCompiler } from "../engine/item-compiler.mjs";
|
|
54
|
+
import { HM3_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
55
|
+
import { templateFlags } from "./template-priority.mjs";
|
|
56
|
+
|
|
57
|
+
export class Hm3Items extends SystemItemCompiler {
|
|
58
|
+
/**
|
|
59
|
+
* HM3's note-type → document-subtype map — the one declaration that says
|
|
60
|
+
* which block this pass reads, which notes it claims, and what each becomes
|
|
61
|
+
* (#58/#79).
|
|
62
|
+
*
|
|
63
|
+
* @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
|
|
64
|
+
*/
|
|
65
|
+
static documentSubtypes = HM3_DOCUMENT_SUBTYPES;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The `system.*` field HM3 writes on an item from the note's prose.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} fm - The note's frontmatter.
|
|
71
|
+
* @param {object} at - What the pass already knows about this note.
|
|
72
|
+
* @param {string} at.markdown - The note body, tables expanded and
|
|
73
|
+
* wikilinks resolved.
|
|
74
|
+
* @returns {object} The shared `system` fields — `description`, or nothing.
|
|
75
|
+
*/
|
|
76
|
+
commonSystem(fm, { markdown }) {
|
|
77
|
+
const description = renderSection(markdown, "appearance");
|
|
78
|
+
return description ? { description } : {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The `flags` HM3 writes on an item: whatever the note authors, plus the
|
|
83
|
+
* template priority.
|
|
84
|
+
*
|
|
85
|
+
* The same statement the Actor pass records, through the same
|
|
86
|
+
* {@link module:hm3/template-priority.templateFlags} — see that module for
|
|
87
|
+
* why the priority lives in flags at all, and for what its absence here
|
|
88
|
+
* cost (#283).
|
|
89
|
+
*
|
|
90
|
+
* @param {object} fm - The note's frontmatter.
|
|
91
|
+
* @returns {object} The flags to emit.
|
|
92
|
+
*/
|
|
93
|
+
commonFlags(fm) {
|
|
94
|
+
return templateFlags(fm, this.system);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
* **Where HM3 records the template priority** — one statement, read by both of
|
|
16
|
+
* this system's passes (#283).
|
|
17
|
+
*
|
|
18
|
+
* `data.templatePriority` is the shared fact that a note is a *starting
|
|
19
|
+
* template*, and the specification states it as a row every type maps:
|
|
20
|
+
* `system.templatePriority` in SoHL, `flags.hm3.templatePriority` here. It lands
|
|
21
|
+
* in flags because HM3's data model declares no field for it, and an undeclared
|
|
22
|
+
* `system` key is discarded at load without a word.
|
|
23
|
+
*
|
|
24
|
+
* It was written by the Actor pass alone. The Item pass emitted only whatever
|
|
25
|
+
* `flags` the note itself authored, so an item note declaring the priority
|
|
26
|
+
* compiled into a SoHL item that knew it was a template and an HM3 item that did
|
|
27
|
+
* not — silently on both sides of the build, since an omitted flag is exactly how
|
|
28
|
+
* this system says *not a template*, making a lost priority and a deliberate one
|
|
29
|
+
* the same output. Nothing could report it either: the emitted-`system` check
|
|
30
|
+
* compares against a declared schema, and a flag is declared by nothing.
|
|
31
|
+
*
|
|
32
|
+
* So the rule lives here rather than in either pass, and both call it. Two
|
|
33
|
+
* copies of it would be two chances to diverge again, which is the failure this
|
|
34
|
+
* module exists to close.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import log from "loglevel";
|
|
40
|
+
|
|
41
|
+
import { resolveName, statedTemplatePriority } from "../engine/helpers.mjs";
|
|
42
|
+
import { blockProperty } from "../engine/system-block.mjs";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A document's `flags`: whatever the note authors, plus this system's template
|
|
46
|
+
* priority.
|
|
47
|
+
*
|
|
48
|
+
* A note that is not a template — or states nothing — writes nothing, rather
|
|
49
|
+
* than a `null` nothing reads.
|
|
50
|
+
*
|
|
51
|
+
* **Read through the shared resolver, not a field declaration** (#266). A
|
|
52
|
+
* `FieldSpec`'s shared source is a single position, and this value has five:
|
|
53
|
+
* `data:`, this block, the top level, and the retiring `archetype` spelling in
|
|
54
|
+
* the latter two. The resolver is the single implementation of what a note said,
|
|
55
|
+
* so the two systems cannot disagree about it. It is read against **this**
|
|
56
|
+
* block: the `sohl:` block is not a source for an HM3 document, so a tree that
|
|
57
|
+
* still states the priority there (`harn-ensemble`, on 2,502 notes) writes no
|
|
58
|
+
* HM3 flag until it sweeps to `data:`.
|
|
59
|
+
*
|
|
60
|
+
* @param {object} fm - The note's frontmatter.
|
|
61
|
+
* @param {string} block - This pass's system block.
|
|
62
|
+
* @returns {object} The flags to emit.
|
|
63
|
+
*/
|
|
64
|
+
export function templateFlags(fm, block) {
|
|
65
|
+
const authored = blockProperty(fm, block, "flags", {});
|
|
66
|
+
const stated = statedTemplatePriority(fm, resolveName(fm), { block });
|
|
67
|
+
if (stated == null) return authored;
|
|
68
|
+
// Coerced, as the field declaration this replaced coerced it: a note may
|
|
69
|
+
// state the priority as a YAML string, and `"3"` is a priority.
|
|
70
|
+
const value = Number(stated);
|
|
71
|
+
if (!Number.isFinite(value)) {
|
|
72
|
+
log.warn(
|
|
73
|
+
`${resolveName(fm)}: template priority ${JSON.stringify(stated)} is ` +
|
|
74
|
+
`not a number; no template flag written.`,
|
|
75
|
+
);
|
|
76
|
+
return authored;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...authored,
|
|
80
|
+
[block]: {
|
|
81
|
+
.../** @type {Record<string, unknown>} */ (authored)[block],
|
|
82
|
+
templatePriority: value,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
package/manifest.mjs
CHANGED
|
@@ -53,6 +53,7 @@ import fsSync from "node:fs";
|
|
|
53
53
|
import path from "node:path";
|
|
54
54
|
|
|
55
55
|
import { emitDiagnostic, positionOfYamlPath } from "./engine/diagnostics.mjs";
|
|
56
|
+
import { metadataFileName } from "./engine/metadata-index.mjs";
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* The two package kinds Foundry defines, as the artifact name each one's
|
|
@@ -97,7 +98,7 @@ export function normalizeRepoUrl(repository) {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
/**
|
|
100
|
-
* The
|
|
101
|
+
* The addresses a Foundry manifest advertises.
|
|
101
102
|
*
|
|
102
103
|
* `manifest` deliberately points at **`releases/latest`** rather than at this
|
|
103
104
|
* version: it is the URL an *installed* package re-fetches to discover that a
|
|
@@ -120,6 +121,26 @@ export function releaseUrls({ repoUrl, version, artifact }) {
|
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Where this release publishes its content index (#239).
|
|
126
|
+
*
|
|
127
|
+
* **Pinned to this version, like `download` and unlike `manifest`.** A
|
|
128
|
+
* consumer reaches this URL by reading the dependency's manifest, so the
|
|
129
|
+
* manifest it read and the index it then fetches describe the same release —
|
|
130
|
+
* which is the whole point of publishing them together. A `releases/latest`
|
|
131
|
+
* index would silently pair a pinned manifest with a moving index, and the
|
|
132
|
+
* mismatch would surface as a cross-package link that resolved yesterday.
|
|
133
|
+
*
|
|
134
|
+
* @param {object} opts
|
|
135
|
+
* @param {string} opts.repoUrl - Normalised repository URL.
|
|
136
|
+
* @param {string} opts.version - The version being built.
|
|
137
|
+
* @param {string} opts.contentPackage - The content package name.
|
|
138
|
+
* @returns {string} The version-pinned asset URL.
|
|
139
|
+
*/
|
|
140
|
+
export function metadataUrl({ repoUrl, version, contentPackage }) {
|
|
141
|
+
return `${repoUrl}/releases/download/v${version}/${metadataFileName(contentPackage)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
123
144
|
/**
|
|
124
145
|
* The order the manifest's keys are written in.
|
|
125
146
|
*
|
|
@@ -455,8 +476,28 @@ export function buildManifest({ config, packageJson, artifact, flags }) {
|
|
|
455
476
|
|
|
456
477
|
const merged = { ...declared, ...derived };
|
|
457
478
|
|
|
479
|
+
// The index every consumer resolves this package's addresses through
|
|
480
|
+
// (#239). Written unconditionally, because a package that publishes no
|
|
481
|
+
// index is one nothing can link into — and the failure of an absent one is
|
|
482
|
+
// a dead link in somebody else's build, which is exactly the kind of
|
|
483
|
+
// silence this replaced the vendored manifest to end.
|
|
484
|
+
//
|
|
485
|
+
// A flag rather than a top-level key because Foundry's manifest schema is
|
|
486
|
+
// closed and `flags` is its declared extension point; an unknown key at the
|
|
487
|
+
// top level is dropped by some readers and rejected by others.
|
|
488
|
+
if (config.contentPackage) {
|
|
489
|
+
merged.flags = {
|
|
490
|
+
...(declared.flags ?? {}),
|
|
491
|
+
metadataUrl: metadataUrl({
|
|
492
|
+
repoUrl,
|
|
493
|
+
version: packageJson.version,
|
|
494
|
+
contentPackage: config.contentPackage,
|
|
495
|
+
}),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
458
499
|
if (flags && Object.keys(flags).length) {
|
|
459
|
-
merged.flags = { ...(declared.flags ?? {}) };
|
|
500
|
+
merged.flags = { ...(merged.flags ?? declared.flags ?? {}) };
|
|
460
501
|
for (const [namespace, values] of Object.entries(flags)) {
|
|
461
502
|
merged.flags[namespace] = {
|
|
462
503
|
...(declared.flags?.[namespace] ?? {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "18.0.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",
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
"types": "./types/sohl/*.d.mts",
|
|
28
28
|
"import": "./sohl/*.mjs"
|
|
29
29
|
},
|
|
30
|
+
"./hm3": {
|
|
31
|
+
"types": "./types/hm3/index.d.mts",
|
|
32
|
+
"import": "./hm3/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./hm3/*": {
|
|
35
|
+
"types": "./types/hm3/*.d.mts",
|
|
36
|
+
"import": "./hm3/*.mjs"
|
|
37
|
+
},
|
|
30
38
|
"./content-config": {
|
|
31
39
|
"types": "./types/content-config.d.mts",
|
|
32
40
|
"import": "./content-config.mjs"
|
|
@@ -105,6 +113,7 @@
|
|
|
105
113
|
"deploy.mjs",
|
|
106
114
|
"e2e.mjs",
|
|
107
115
|
"engine",
|
|
116
|
+
"hm3",
|
|
108
117
|
"index.mjs",
|
|
109
118
|
"lang.mjs",
|
|
110
119
|
"labels.mjs",
|
|
@@ -118,11 +127,14 @@
|
|
|
118
127
|
"types"
|
|
119
128
|
],
|
|
120
129
|
"dependencies": {
|
|
130
|
+
"@duckdb/node-api": "^1.5.5-r.4",
|
|
121
131
|
"@foundryvtt/foundryvtt-cli": "^3.0.4",
|
|
122
132
|
"acorn": "^8.18.0",
|
|
123
133
|
"archiver": "^8.0.0",
|
|
124
134
|
"classic-level": "^3.0.0",
|
|
125
135
|
"dotenv": "^17.2.3",
|
|
136
|
+
"eslint": "^10.10.0",
|
|
137
|
+
"eslint-plugin-yml": "^3.8.1",
|
|
126
138
|
"fflate": "^0.8.3",
|
|
127
139
|
"glob": "^13.0.6",
|
|
128
140
|
"gray-matter": "^4.0.3",
|
|
@@ -150,12 +162,14 @@
|
|
|
150
162
|
"build:types": "tsc -p tsconfig.dts.json",
|
|
151
163
|
"format": "prettier --write .",
|
|
152
164
|
"format:check": "prettier --check .",
|
|
153
|
-
"lint": "npm run format:check && npm run lint:markdown && npm run lint:content-format",
|
|
165
|
+
"lint": "npm run format:check && npm run lint:markdown && npm run lint:yaml && npm run lint:labels && npm run lint:content-format",
|
|
154
166
|
"lint:markdown": "node bin/content-build.mjs markdown",
|
|
167
|
+
"lint:yaml": "node bin/package-build.mjs yaml",
|
|
168
|
+
"lint:labels": "node bin/package-build.mjs labels check",
|
|
155
169
|
"lint:markdown:fix": "node bin/content-build.mjs markdown --fix",
|
|
156
170
|
"lint:content-format": "npm run lint:content-format:schema && npm run lint:content-format:fields",
|
|
157
|
-
"lint:content-format:schema": "node bin/content-build.mjs content-format schema --schema sohl=tests/fixtures/content-format/schema-sohl.json",
|
|
158
|
-
"lint:content-format:fields": "node bin/content-build.mjs content-format fields --fields sohl",
|
|
171
|
+
"lint:content-format:schema": "node bin/content-build.mjs content-format schema --schema sohl=tests/fixtures/content-format/schema-sohl.json --schema hm3=tests/fixtures/content-format/schema-hm3.json",
|
|
172
|
+
"lint:content-format:fields": "node bin/content-build.mjs content-format fields --fields sohl && node bin/content-build.mjs content-format fields --fields hm3",
|
|
159
173
|
"changeset": "changeset",
|
|
160
174
|
"changeset:check": "changeset status --since=origin/main",
|
|
161
175
|
"changeset:version": "changeset version && npm install --package-lock-only",
|