@heroiclands/package-build 16.0.0 → 17.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 +42 -0
- package/docs/content-format.md +7 -1
- package/engine/note-schemas.mjs +43 -0
- package/engine/note-vocabulary.mjs +119 -2
- package/package.json +1 -1
- package/sohl/note-schemas.mjs +17 -0
- package/types/engine/note-vocabulary.d.mts +50 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 17.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- fdd7094: **The linter now implements the format it publishes.** `docs/content-format.md`
|
|
8
|
+
and the vocabulary had drifted, silently and in both directions, and nothing
|
|
9
|
+
compared them.
|
|
10
|
+
|
|
11
|
+
**Five documented types reached no schema** — `place`, `lore`, `scenario`,
|
|
12
|
+
`vehicle` and `armorlocation`. A note using one was reported as having no
|
|
13
|
+
schema and then _skipped entirely_: `lintNote` returns after that finding, so
|
|
14
|
+
the note's `data:`, `subType`, references and system block all went
|
|
15
|
+
unexamined. So the types were legal enough to author and not legal enough to
|
|
16
|
+
check, and using one **suppressed** every other check on the note (#231).
|
|
17
|
+
|
|
18
|
+
**Three documented `data` properties reached no vocabulary** — `epithet` and
|
|
19
|
+
`symbol` on `affiliation`, and `lore` on both `affiliation` and `being`. The
|
|
20
|
+
`data:` container is closed, so a note that followed the specification exactly
|
|
21
|
+
was told its property did not exist _and the value was dropped_ rather than
|
|
22
|
+
reaching the page (#232).
|
|
23
|
+
|
|
24
|
+
`peoples` is **removed**, having widened to `lore` — the specification says so
|
|
25
|
+
outright, and no tree authors it: "Nothing is lost by widening: the target's
|
|
26
|
+
own subType already distinguishes a `folk` from a `law`." That removal is the
|
|
27
|
+
reason this is a major; everything else here turns red trees green.
|
|
28
|
+
|
|
29
|
+
Between them, on `sohl-thalorna`: **2,001 findings become 20**. The 1,981 that
|
|
30
|
+
go were never content defects. The 20 that stay are: 18 embedded shortcode
|
|
31
|
+
collisions and two genuinely stale keys.
|
|
32
|
+
|
|
33
|
+
**The specification is executable now.** `tests/content-format-agreement.test.ts`
|
|
34
|
+
parses `docs/content-format.md` and fails if a documented type has no schema or
|
|
35
|
+
no vocabulary, or if a type's declared `data` properties differ in either
|
|
36
|
+
direction from its documented table. Nothing compared the two before, which is
|
|
37
|
+
why this drift lasted.
|
|
38
|
+
|
|
39
|
+
Also corrects the specification itself: `macro`'s `macroType` and `macroScope`
|
|
40
|
+
were tabled as `data` properties, but the compiler reads them with the
|
|
41
|
+
`sohl`-field accessor — the `sohl:` block, then the note's top level — so
|
|
42
|
+
`data.macroType` is not read at all. They are `sohl` properties, and the table
|
|
43
|
+
now says so.
|
|
44
|
+
|
|
3
45
|
## 16.0.0
|
|
4
46
|
|
|
5
47
|
### Major Changes
|
package/docs/content-format.md
CHANGED
|
@@ -1463,7 +1463,13 @@ shipped as content and run by Foundry's own macro runner under the permission
|
|
|
1463
1463
|
model that governs every macro in a world. Nothing evaluates, compiles, or
|
|
1464
1464
|
revives anything; the compiler copies text from a fence into a JSON field.
|
|
1465
1465
|
|
|
1466
|
-
|
|
1466
|
+
**Both settings are `sohl` properties, not `data` ones.** The compiler reads
|
|
1467
|
+
them with the same accessor every `sohl` field uses — the `sohl:` block first,
|
|
1468
|
+
then the note's top level — so `data.macroType` is not read, and a macro is not
|
|
1469
|
+
a journal-only note the way `place`, `lore` and `scenario` are: it produces a
|
|
1470
|
+
Foundry **Macro**, and these two describe that document.
|
|
1471
|
+
|
|
1472
|
+
| `sohl` property | Values | Description |
|
|
1467
1473
|
| --------------- | --------------------------- | -------------------------------------------------------------------- |
|
|
1468
1474
|
| `macroType` | `script` | The Foundry macro type. Defaults to `script`, and `chat` is an error |
|
|
1469
1475
|
| `macroScope` | `global \| actors \| actor` | How far the macro reaches. Defaults to `global` |
|
package/engine/note-schemas.mjs
CHANGED
|
@@ -34,6 +34,45 @@
|
|
|
34
34
|
|
|
35
35
|
import { HOMEPAGE_FIELDS, HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Every engine-level content type, and what a note of that type may write.
|
|
39
|
+
*
|
|
40
|
+
* @type {Readonly<Record<string, readonly import("./field-spec.mjs").FieldSpec[]>>}
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* A note that compiles to a JournalEntry and nothing else.
|
|
44
|
+
*
|
|
45
|
+
* Empty on purpose, and the emptiness is the declaration. `place`, `lore` and
|
|
46
|
+
* `scenario` produce prose — the JournalEntry every note produces — so none of
|
|
47
|
+
* them writes a `sohl:` field, and the authored corpus agrees: across
|
|
48
|
+
* `sohl-thalorna`'s 249 places, 180 lore notes and 21 scenarios, not one
|
|
49
|
+
* carries a `sohl:` block. Everything they *do* declare is `data:`, which is
|
|
50
|
+
* the closed container `engine/note-vocabulary.mjs` holds them to.
|
|
51
|
+
*
|
|
52
|
+
* Declaring the type with no fields is what distinguishes **a type with no
|
|
53
|
+
* vocabulary** from **a type that is unknown** — two different findings, and
|
|
54
|
+
* only the second is a mistake. Until this landed the specification declared
|
|
55
|
+
* all three and nothing implemented them, so a note using one was reported as
|
|
56
|
+
* having no schema and then *skipped entirely*: `lintNote` returns after that
|
|
57
|
+
* finding, so the note's `data:`, `subType`, references and system block all
|
|
58
|
+
* went unexamined (#231).
|
|
59
|
+
*
|
|
60
|
+
* @type {readonly import("./field-spec.mjs").FieldSpec[]}
|
|
61
|
+
*/
|
|
62
|
+
const JOURNAL_ONLY_FIELDS = Object.freeze([]);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* An `armorlocation` note — an HM3 item, and no part of the SoHL registry.
|
|
66
|
+
*
|
|
67
|
+
* It lives here rather than in `sohl/` for the reachability reason that
|
|
68
|
+
* governs this whole file: a package declaring no `itemBuilders` never loads
|
|
69
|
+
* the SoHL registry, and HM3 packages are exactly the ones that author this
|
|
70
|
+
* type.
|
|
71
|
+
*
|
|
72
|
+
* @type {readonly import("./field-spec.mjs").FieldSpec[]}
|
|
73
|
+
*/
|
|
74
|
+
const ARMORLOCATION_FIELDS = Object.freeze([]);
|
|
75
|
+
|
|
37
76
|
/**
|
|
38
77
|
* Every engine-level content type, and what a note of that type may write.
|
|
39
78
|
*
|
|
@@ -41,4 +80,8 @@ import { HOMEPAGE_FIELDS, HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
|
41
80
|
*/
|
|
42
81
|
export const ENGINE_NOTE_SCHEMAS = Object.freeze({
|
|
43
82
|
[HOMEPAGE_TYPE]: HOMEPAGE_FIELDS,
|
|
83
|
+
place: JOURNAL_ONLY_FIELDS,
|
|
84
|
+
lore: JOURNAL_ONLY_FIELDS,
|
|
85
|
+
scenario: JOURNAL_ONLY_FIELDS,
|
|
86
|
+
armorlocation: ARMORLOCATION_FIELDS,
|
|
44
87
|
});
|
|
@@ -395,7 +395,13 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
395
395
|
{ name: "archetypes", ...LIST, describe: "Archetypal behaviours the being fits." },
|
|
396
396
|
{ name: "occupation", ...TEXT, describe: "What the being does for a living." },
|
|
397
397
|
{ name: "stations", ...LINKS, describe: "Stations the being holds." },
|
|
398
|
-
{
|
|
398
|
+
{
|
|
399
|
+
name: "lore",
|
|
400
|
+
...LINKS,
|
|
401
|
+
describe:
|
|
402
|
+
"Lore concerning this being — the people it is of, the standing it " +
|
|
403
|
+
"holds, the law it lives under.",
|
|
404
|
+
},
|
|
399
405
|
{ name: "homes", ...LINKS, describe: "Places the being calls home." },
|
|
400
406
|
{
|
|
401
407
|
name: "affiliations",
|
|
@@ -425,6 +431,14 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
425
431
|
]),
|
|
426
432
|
}),
|
|
427
433
|
|
|
434
|
+
vehicle: Object.freeze({
|
|
435
|
+
subTypes: null,
|
|
436
|
+
data: Object.freeze([
|
|
437
|
+
{ name: "portrait", ...TEXT, describe: "Path to the portrait image." },
|
|
438
|
+
TEMPLATE_PRIORITY,
|
|
439
|
+
]),
|
|
440
|
+
}),
|
|
441
|
+
|
|
428
442
|
/* ----- items ---------------------------------------------------- */
|
|
429
443
|
|
|
430
444
|
affiliation: Object.freeze({
|
|
@@ -448,6 +462,18 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
448
462
|
...TEXT,
|
|
449
463
|
describe: "What one member is called — a Vylarian.",
|
|
450
464
|
},
|
|
465
|
+
{
|
|
466
|
+
name: "epithet",
|
|
467
|
+
...TEXT,
|
|
468
|
+
describe: "The by-name it is known by — a god's, an order's, a company's.",
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "symbol",
|
|
472
|
+
...TEXT,
|
|
473
|
+
describe:
|
|
474
|
+
"Its emblem in words: a feather atop a golden scale, a chisel carving " +
|
|
475
|
+
"a star.",
|
|
476
|
+
},
|
|
451
477
|
{
|
|
452
478
|
name: "governance.model",
|
|
453
479
|
...TEXT,
|
|
@@ -481,7 +507,13 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
481
507
|
...LINKS,
|
|
482
508
|
describe: "What its economic life runs on — currencies, banking bodies, goods.",
|
|
483
509
|
},
|
|
484
|
-
{
|
|
510
|
+
{
|
|
511
|
+
name: "lore",
|
|
512
|
+
...LINKS,
|
|
513
|
+
describe:
|
|
514
|
+
"Lore concerning it — the peoples it draws on, the god a faith " +
|
|
515
|
+
"venerates, its law, its calendar.",
|
|
516
|
+
},
|
|
485
517
|
{ name: "parents", ...LINKS, describe: "Affiliations it is subordinate to." },
|
|
486
518
|
{
|
|
487
519
|
name: "relations",
|
|
@@ -532,6 +564,11 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
532
564
|
data: Object.freeze([TEMPLATE_PRIORITY, ...GEAR]),
|
|
533
565
|
}),
|
|
534
566
|
|
|
567
|
+
armorlocation: Object.freeze({
|
|
568
|
+
subTypes: null,
|
|
569
|
+
data: Object.freeze([TEMPLATE_PRIORITY]),
|
|
570
|
+
}),
|
|
571
|
+
|
|
535
572
|
attribute: Object.freeze({
|
|
536
573
|
data: Object.freeze([TEMPLATE_PRIORITY]),
|
|
537
574
|
}),
|
|
@@ -674,6 +711,86 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
674
711
|
|
|
675
712
|
macro: Object.freeze({ data: Object.freeze([]) }),
|
|
676
713
|
|
|
714
|
+
lore: Object.freeze({
|
|
715
|
+
subTypes: Object.freeze([
|
|
716
|
+
"cosmology",
|
|
717
|
+
"deity",
|
|
718
|
+
"theology",
|
|
719
|
+
"arcana",
|
|
720
|
+
"spirit",
|
|
721
|
+
"economy",
|
|
722
|
+
"law",
|
|
723
|
+
"calendar",
|
|
724
|
+
"history",
|
|
725
|
+
"material",
|
|
726
|
+
"folk",
|
|
727
|
+
"culture",
|
|
728
|
+
"bestiary",
|
|
729
|
+
]),
|
|
730
|
+
// Nothing of its own: a lore note is prose, and what it *is* about is
|
|
731
|
+
// its subType. The specification declares an empty table for it, and
|
|
732
|
+
// the authored corpus writes no `data:` key on any of the 180.
|
|
733
|
+
data: Object.freeze([]),
|
|
734
|
+
}),
|
|
735
|
+
|
|
736
|
+
place: Object.freeze({
|
|
737
|
+
subTypes: Object.freeze(["world", "region", "settlement", "site", "structure", "feature"]),
|
|
738
|
+
data: Object.freeze([
|
|
739
|
+
{
|
|
740
|
+
name: "demonym",
|
|
741
|
+
...TEXT,
|
|
742
|
+
describe: "What a person from this place is called — a Vylarian.",
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "lore",
|
|
746
|
+
...LINKS,
|
|
747
|
+
describe:
|
|
748
|
+
"Lore concerning this place — its peoples, its law, its calendar, " +
|
|
749
|
+
"its history.",
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "parents",
|
|
753
|
+
...LINKS,
|
|
754
|
+
describe: "Enclosing places this one sits within.",
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
name: "population",
|
|
758
|
+
...NUM,
|
|
759
|
+
describe: "Approximate population, to two significant digits.",
|
|
760
|
+
},
|
|
761
|
+
]),
|
|
762
|
+
}),
|
|
763
|
+
|
|
764
|
+
scenario: Object.freeze({
|
|
765
|
+
subTypes: Object.freeze(["campaign", "adventure", "encounter"]),
|
|
766
|
+
data: Object.freeze([
|
|
767
|
+
{ name: "parents", ...LINKS, describe: "Scenarios this one sits within." },
|
|
768
|
+
{ name: "locations", ...LINKS, describe: "Places the scenario takes place in." },
|
|
769
|
+
{ name: "cast", ...LINKS, describe: "Beings who appear in it." },
|
|
770
|
+
{ name: "factions", ...LINKS, describe: "Affiliations with a stake in it." },
|
|
771
|
+
{
|
|
772
|
+
name: "follows",
|
|
773
|
+
...LINKS,
|
|
774
|
+
describe: "Scenarios that should be played before this one.",
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
name: "status",
|
|
778
|
+
...TEXT,
|
|
779
|
+
describe: "`draft`, `playtested` or `published`.",
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
name: "party.size",
|
|
783
|
+
...TEXT,
|
|
784
|
+
describe: "`solo`, `small`, `standard`, `large` or `host`.",
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
name: "party.archetypes",
|
|
788
|
+
...LIST,
|
|
789
|
+
describe: "Archetypes the scenario is written for.",
|
|
790
|
+
},
|
|
791
|
+
]),
|
|
792
|
+
}),
|
|
793
|
+
|
|
677
794
|
homepage: Object.freeze({ data: Object.freeze([]) }),
|
|
678
795
|
|
|
679
796
|
map: Object.freeze({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.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",
|
package/sohl/note-schemas.mjs
CHANGED
|
@@ -87,6 +87,22 @@ const MACRO_FIELDS = Object.freeze([
|
|
|
87
87
|
*
|
|
88
88
|
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
89
89
|
*/
|
|
90
|
+
/**
|
|
91
|
+
* A `vehicle` note — a conveyance that carries goods and people.
|
|
92
|
+
*
|
|
93
|
+
* Empty, and the emptiness is the declaration: the specification gives a
|
|
94
|
+
* vehicle two `data` properties and no `sohl:` field of its own, and the
|
|
95
|
+
* closed `data:` container is where `engine/note-vocabulary.mjs` holds them.
|
|
96
|
+
* Declaring the type with no fields distinguishes a type with no vocabulary
|
|
97
|
+
* from a type that is unknown, which are different findings (#231).
|
|
98
|
+
*
|
|
99
|
+
* It is here rather than in the engine because a vehicle is a SoHL actor —
|
|
100
|
+
* the specification maps it to `sohl` and marks it NA for hm3.
|
|
101
|
+
*
|
|
102
|
+
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
103
|
+
*/
|
|
104
|
+
const VEHICLE_FIELDS = Object.freeze([]);
|
|
105
|
+
|
|
90
106
|
const BEING_FIELDS = Object.freeze([
|
|
91
107
|
{
|
|
92
108
|
name: "body",
|
|
@@ -330,5 +346,6 @@ export const NOTE_SCHEMAS = Object.freeze({
|
|
|
330
346
|
doc: DOC_FIELDS,
|
|
331
347
|
macro: MACRO_FIELDS,
|
|
332
348
|
being: Object.freeze([...BEING_FIELDS, ...PRESENTATION_FIELDS.being]),
|
|
349
|
+
vehicle: VEHICLE_FIELDS,
|
|
333
350
|
map: MAP_FIELDS,
|
|
334
351
|
});
|
|
@@ -187,6 +187,10 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
187
187
|
subTypes: null;
|
|
188
188
|
data: readonly DataFieldSpec[];
|
|
189
189
|
}>;
|
|
190
|
+
vehicle: Readonly<{
|
|
191
|
+
subTypes: null;
|
|
192
|
+
data: readonly DataFieldSpec[];
|
|
193
|
+
}>;
|
|
190
194
|
affiliation: Readonly<{
|
|
191
195
|
subTypes: readonly string[];
|
|
192
196
|
data: readonly DataFieldSpec[];
|
|
@@ -198,6 +202,10 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
198
202
|
armorgear: Readonly<{
|
|
199
203
|
data: readonly DataFieldSpec[];
|
|
200
204
|
}>;
|
|
205
|
+
armorlocation: Readonly<{
|
|
206
|
+
subTypes: null;
|
|
207
|
+
data: readonly DataFieldSpec[];
|
|
208
|
+
}>;
|
|
201
209
|
attribute: Readonly<{
|
|
202
210
|
data: readonly DataFieldSpec[];
|
|
203
211
|
}>;
|
|
@@ -256,6 +264,48 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
256
264
|
macro: Readonly<{
|
|
257
265
|
data: readonly never[];
|
|
258
266
|
}>;
|
|
267
|
+
lore: Readonly<{
|
|
268
|
+
subTypes: readonly string[];
|
|
269
|
+
data: readonly never[];
|
|
270
|
+
}>;
|
|
271
|
+
place: Readonly<{
|
|
272
|
+
subTypes: readonly string[];
|
|
273
|
+
data: readonly ({
|
|
274
|
+
describe: string;
|
|
275
|
+
shape: "string";
|
|
276
|
+
kind: "string";
|
|
277
|
+
name: string;
|
|
278
|
+
} | {
|
|
279
|
+
describe: string;
|
|
280
|
+
shape: "list of wikilinks";
|
|
281
|
+
kind: "list";
|
|
282
|
+
name: string;
|
|
283
|
+
} | {
|
|
284
|
+
describe: string;
|
|
285
|
+
shape: "number";
|
|
286
|
+
kind: "number";
|
|
287
|
+
name: string;
|
|
288
|
+
})[];
|
|
289
|
+
}>;
|
|
290
|
+
scenario: Readonly<{
|
|
291
|
+
subTypes: readonly string[];
|
|
292
|
+
data: readonly ({
|
|
293
|
+
describe: string;
|
|
294
|
+
shape: "list of wikilinks";
|
|
295
|
+
kind: "list";
|
|
296
|
+
name: string;
|
|
297
|
+
} | {
|
|
298
|
+
describe: string;
|
|
299
|
+
shape: "string";
|
|
300
|
+
kind: "string";
|
|
301
|
+
name: string;
|
|
302
|
+
} | {
|
|
303
|
+
describe: string;
|
|
304
|
+
shape: "list";
|
|
305
|
+
kind: "list";
|
|
306
|
+
name: string;
|
|
307
|
+
})[];
|
|
308
|
+
}>;
|
|
259
309
|
homepage: Readonly<{
|
|
260
310
|
data: readonly never[];
|
|
261
311
|
}>;
|