@heroiclands/package-build 22.5.1 → 22.6.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 +12 -0
- package/CONTENT.md +19 -7
- package/docs/api.md +3 -0
- package/docs/content-format.md +81 -14
- package/engine/actor-compiler.mjs +12 -0
- package/engine/base-compiler.mjs +51 -36
- package/engine/foundry-entries.mjs +25 -7
- package/engine/generate.mjs +81 -10
- package/engine/infobox-registry.mjs +14 -13
- package/engine/item-compiler.mjs +12 -0
- package/engine/note-claims.mjs +98 -2
- package/engine/subtype-registry.mjs +42 -5
- package/package.json +1 -1
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/base-compiler.d.mts +46 -16
- package/types/engine/generate.d.mts +29 -0
- package/types/engine/infobox-registry.d.mts +11 -12
- package/types/engine/item-compiler.d.mts +11 -0
- package/types/engine/note-claims.d.mts +6 -1
- package/types/engine/subtype-registry.d.mts +32 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
**Compendium content**
|
|
8
|
+
|
|
9
|
+
- A person or thing with no game-system data no longer becomes a blank
|
|
10
|
+
compendium entry — the note still publishes its page and its journal, so
|
|
11
|
+
somebody can be written without being statted.
|
|
12
|
+
- A package carrying data for a game system it ships no compendium for is now
|
|
13
|
+
told so, rather than having it quietly dropped.
|
|
14
|
+
|
|
3
15
|
## 22.5.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/CONTENT.md
CHANGED
|
@@ -867,7 +867,12 @@ has no art.
|
|
|
867
867
|
A note is **system-agnostic**. The only system-specific things it carries are
|
|
868
868
|
the properties named after a system, and one note may carry more than one — a
|
|
869
869
|
`being` in `harn-ensemble` compiles into a SoHL `being` _and_ an HM3
|
|
870
|
-
`character`.
|
|
870
|
+
`character`.
|
|
871
|
+
|
|
872
|
+
**The block is what makes a game document.** A note carrying none produces no
|
|
873
|
+
Actor and no Item at all, in any package; its prose still compiles into a page,
|
|
874
|
+
a PDF page and a JournalEntry, so a person can be written without being statted.
|
|
875
|
+
Within a system's block:
|
|
871
876
|
|
|
872
877
|
| property | maps to |
|
|
873
878
|
| ------------------ | ------------------------------------------------------------- |
|
|
@@ -1006,12 +1011,19 @@ hold this package at a build that does not write it. A subtree the schema
|
|
|
1006
1011
|
declares but describes no further — a discriminated `TypedSchemaField`, stored
|
|
1007
1012
|
flat — is left alone rather than reported wholesale.
|
|
1008
1013
|
|
|
1009
|
-
**
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1014
|
+
**An Actor or Item pass takes only notes carrying the block it reads.** A note
|
|
1015
|
+
that says nothing about a system has no system data, and compiling it would emit
|
|
1016
|
+
a hollow document — a subtype, and none of the fields the subtype exists for. So
|
|
1017
|
+
a note carrying no block for any system produces no Actor and no Item anywhere,
|
|
1018
|
+
and is passed over in silence; its prose still compiles into a page, a PDF page
|
|
1019
|
+
and a JournalEntry. The question is asked of the pass's own system rather than
|
|
1020
|
+
of the pack's `system:`: a pack declaring none is compiled by the fallback pass,
|
|
1021
|
+
which reads one block like any other. A pass whose document is not system data
|
|
1022
|
+
at all — journals, macros, scenes — is not subject to the rule.
|
|
1023
|
+
|
|
1024
|
+
**A block for a system no pack of the class compiles is an error**, reported
|
|
1025
|
+
once per note per system with the position of the block key, because the
|
|
1026
|
+
authored data is discarded either way.
|
|
1015
1027
|
|
|
1016
1028
|
**`(type, shortcode)` resolves inside one system's catalogue.** A being names its
|
|
1017
1029
|
embedded items by address and never by pack, so the Item packs are read as one
|
package/docs/api.md
CHANGED
|
@@ -627,6 +627,8 @@ Which note-type → document-subtype maps this toolchain ships, and the two ques
|
|
|
627
627
|
| `KNOWN_DOCUMENT_SUBTYPE_MAPS` | `const KNOWN_DOCUMENT_SUBTYPE_MAPS` | — | The note-type → document-subtype maps this toolchain ships. |
|
|
628
628
|
| `ACTOR_TYPES` | `const ACTOR_TYPES` | — | Every note type any shipped map compiles into an **Actor**. |
|
|
629
629
|
| `DEFAULT_DOCUMENT_SUBTYPES` | `const DEFAULT_DOCUMENT_SUBTYPES` | — | The map a pack declaring no `system:` compiles its documents against. |
|
|
630
|
+
| `SHIPPED_SYSTEMS` | `const SHIPPED_SYSTEMS` | — | Every system this toolchain ships a map for, by id. |
|
|
631
|
+
| `SYSTEM_DOCUMENT_CLASSES` | `const SYSTEM_DOCUMENT_CLASSES` | — | The Foundry document classes a system compiles its own data into. |
|
|
630
632
|
| `subtypeMapFor` | `function subtypeMapFor(system)` | {import("./document-subtypes.mjs").DocumentSubtypeMap\|undefined} Its map, or `undefined` where this toolchain ships none for it. | The map one system ships, by its id. |
|
|
631
633
|
| `schemaSubtypeOf` | `function schemaSubtypeOf(system, type)` | {string} The document subtype to look up. | The document subtype a note type compiles into for one system — the translation the _schema_ check needs, and the reason it needs one. |
|
|
632
634
|
|
|
@@ -1197,6 +1199,7 @@ Pack JSON generation — in-repo Markdown → per-entry JSON (build-only). Reads
|
|
|
1197
1199
|
|
|
1198
1200
|
| Export | Signature | Returns | Use it when |
|
|
1199
1201
|
| ----------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
1202
|
+
| `systemCompilers` | `function systemCompilers(passes)` | {Readonly<Record<string, Readonly<Record<string, Function>>>>} System id → document class → pass. | The system-specific compilers, derived from the passes themselves. |
|
|
1200
1203
|
| `compilerFor` | `function compilerFor(docType, system` | {Function\|undefined} The compiler class, or `undefined` for a document type nothing here compiles — which {@link generatePack} reports rather than defaulting past. | The compiler class a pack of one document type and one system gets. |
|
|
1201
1204
|
| `emittedArtFor` | `function emittedArtFor(type)` | {{document: string\|null, art: readonly string[]}\|null} What the type compiles into and the art it carries there, or `null` where no claim can be made — a retired type, which is reported as retired instead. | The art fields a note of one content type reaches its document through, and the document it reaches. |
|
|
1202
1205
|
| `packJsonDir` | `const packJsonDir` | {string} The pack's JSON directory. | Root of the build-only JSON tree for one pack. |
|
package/docs/content-format.md
CHANGED
|
@@ -197,13 +197,14 @@ _Not available_ on every affiliation page would suggest a gap in the note when
|
|
|
197
197
|
the truth is about the system's scope.
|
|
198
198
|
|
|
199
199
|
Whether a document was produced is the compile's own answer rather than a
|
|
200
|
-
reading of the frontmatter.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
200
|
+
reading of the frontmatter. Three things must hold: the note must carry that
|
|
201
|
+
system's block, its type must map to the system, and it must route to a pack of
|
|
202
|
+
the document class that map names. The block is the first of them because a
|
|
203
|
+
system block is what makes a game document — see
|
|
204
|
+
[A system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
|
|
205
|
+
A pack declaring **no** system is compiled by the fallback pass, which reads one
|
|
206
|
+
system's block and no other, so the question is the same one whether or not the
|
|
207
|
+
pack answered it.
|
|
207
208
|
|
|
208
209
|
#### Four section layouts
|
|
209
210
|
|
|
@@ -788,8 +789,9 @@ _compendium_ receives its document.
|
|
|
788
789
|
— an item's prose compiling into a `JournalEntry` of its own — is not what the
|
|
789
790
|
author was addressing, and is routed by the pass that produces it.
|
|
790
791
|
|
|
791
|
-
**`<system>.pack`
|
|
792
|
-
|
|
792
|
+
**`<system>.pack` names the pack for one system's document.** Routing is per
|
|
793
|
+
system: each block the note carries sends its own document where that block
|
|
794
|
+
says, and a note carrying two blocks may place them in two packs.
|
|
793
795
|
|
|
794
796
|
```yaml
|
|
795
797
|
pack: items-sohl
|
|
@@ -797,9 +799,22 @@ hm3:
|
|
|
797
799
|
pack: items-hm3
|
|
798
800
|
```
|
|
799
801
|
|
|
800
|
-
**Unstated,
|
|
801
|
-
|
|
802
|
-
|
|
802
|
+
**Unstated, a system's document goes to that system's default pack of its
|
|
803
|
+
class** — the pack of the class declaring that `system:`, or, where none does,
|
|
804
|
+
the pack of the class marked `default: true`. Where nothing answers, the build
|
|
805
|
+
refuses rather than guessing, and names the candidates.
|
|
806
|
+
|
|
807
|
+
**`pack` and `packFolder` are different questions.** `pack` names _which
|
|
808
|
+
compendium_ receives a document; `packFolder` names the folder note whose
|
|
809
|
+
address the document is filed under _within_ whichever compendium that is. They
|
|
810
|
+
compose, and both are read per system, so a note may place its two systems'
|
|
811
|
+
documents in different packs and different folders:
|
|
812
|
+
|
|
813
|
+
```yaml
|
|
814
|
+
sohl:
|
|
815
|
+
pack: characteristics
|
|
816
|
+
packFolder: miscgear
|
|
817
|
+
```
|
|
803
818
|
|
|
804
819
|
**`pack: none` compiles the note into no document.** The note is walked,
|
|
805
820
|
published as a page, present in the content index with an address and no
|
|
@@ -1668,6 +1683,10 @@ already carried.
|
|
|
1668
1683
|
| **draft** | written | `tags: [draft]` | yes, marked | yes | yes |
|
|
1669
1684
|
| **full** | written | none | yes | yes | yes |
|
|
1670
1685
|
|
|
1686
|
+
"Document" here is the document a note's frontmatter earns. A system's Actor or
|
|
1687
|
+
Item is earned by that system's block, in every state —
|
|
1688
|
+
[a system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
|
|
1689
|
+
|
|
1671
1690
|
**Full is the absence of everything.** It is the default and carries no marker,
|
|
1672
1691
|
which is right: the overwhelming majority of a finished corpus should say
|
|
1673
1692
|
nothing about its own finishedness.
|
|
@@ -1763,8 +1782,8 @@ cannot be is the target of a wikilink, because a wikilink points at a page.
|
|
|
1763
1782
|
|
|
1764
1783
|
Note types fall into two groups, and only the first has a mapping table.
|
|
1765
1784
|
|
|
1766
|
-
**Types
|
|
1767
|
-
system, described by the tables further down.
|
|
1785
|
+
**Types that _may_ produce a system document** — a SoHL or HM3 Actor or Item,
|
|
1786
|
+
one per system, described by the tables further down.
|
|
1768
1787
|
|
|
1769
1788
|
**Types producing only a core document** — `lore`, `place`, `scenario`, `doc`,
|
|
1770
1789
|
`map`, `macro` and `homepage`. A core document is one Foundry defines rather than
|
|
@@ -1780,6 +1799,54 @@ having nothing worth summarising.
|
|
|
1780
1799
|
Every note in **both** groups still produces its JournalEntry and its web page.
|
|
1781
1800
|
The difference is only whether a system Actor or Item is created as well.
|
|
1782
1801
|
|
|
1802
|
+
#### A system block is what makes a game document
|
|
1803
|
+
|
|
1804
|
+
> **A note produces a system's Actor or Item only where it carries that
|
|
1805
|
+
> system's block.** A note carrying no block for any system produces no Actor
|
|
1806
|
+
> and no Item anywhere, whatever its packs declare.
|
|
1807
|
+
|
|
1808
|
+
The block is not a place to put optional detail — it is the statement that this
|
|
1809
|
+
person or thing is meant to be played. Its absence is a statement too, and it is
|
|
1810
|
+
made silently: the note is passed over by the Actor and Item passes exactly as
|
|
1811
|
+
any note they do not own, with no finding.
|
|
1812
|
+
|
|
1813
|
+
**Prose is untouched by this.** A body with content still compiles into one
|
|
1814
|
+
JournalEntry, one web page and one PDF page, however many blocks the note
|
|
1815
|
+
carries and whether or not it carries any; an empty body still compiles none of
|
|
1816
|
+
the three. So a note has three states, and each reaches the next by one edit in
|
|
1817
|
+
one file, with its address, its id, its index record and every link into it
|
|
1818
|
+
unchanged:
|
|
1819
|
+
|
|
1820
|
+
| The note carries | Produces |
|
|
1821
|
+
| -------------------------- | ---------------------------------------------- |
|
|
1822
|
+
| no block, empty body | an index record — a name that can be queried |
|
|
1823
|
+
| no block, a written body | a page, a PDF page and a JournalEntry |
|
|
1824
|
+
| a block, a written body | all of those, plus that system's Actor or Item |
|
|
1825
|
+
| two blocks, a written body | one document per system, and one of each other |
|
|
1826
|
+
|
|
1827
|
+
Write the person; add a system block when someone will need a token. A setting's
|
|
1828
|
+
cast is mostly people nobody rolls dice against, and a note about one of them
|
|
1829
|
+
carries no block and occupies no slot in a compendium a referee browses.
|
|
1830
|
+
|
|
1831
|
+
**A note carrying no document publishes no UUID for one.** Its address, its
|
|
1832
|
+
page and its documentation journal are published as they are for any other note,
|
|
1833
|
+
and the `foundry` entry that would name an Actor or an Item is absent — the same
|
|
1834
|
+
statement [`pack: none`](#the-pack-a-note-compiles-into) makes about a pack,
|
|
1835
|
+
made about a system.
|
|
1836
|
+
|
|
1837
|
+
**A block for a system this package ships no pack for is an error.** The
|
|
1838
|
+
authored data would be discarded either way, so the build says so, once per note
|
|
1839
|
+
per system, naming the system and the document class:
|
|
1840
|
+
|
|
1841
|
+
```
|
|
1842
|
+
assets/content/People/Aelric.md:7:1: error: carries a `hm3:` block, and no Actor pack in this package compiles hm3 documents, so the block is discarded — declare `system: hm3` on one of the `packs:`, or remove the block.
|
|
1843
|
+
```
|
|
1844
|
+
|
|
1845
|
+
A pack declaring no `system:` is compiled by the fallback pass, which reads one
|
|
1846
|
+
system's block — so a systemless Actor pack serves that system and not every
|
|
1847
|
+
system a note might write. A package shipping for a second system declares
|
|
1848
|
+
`system:` on a pack of each class it compiles.
|
|
1849
|
+
|
|
1783
1850
|
That includes actors. A being producing its Actor and nothing else would be
|
|
1784
1851
|
the one system-bearing note with no address at `none`, leaving a prose link
|
|
1785
1852
|
naming it nowhere to land. It carries a documentation journal like every other
|
|
@@ -501,6 +501,18 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
501
501
|
*/
|
|
502
502
|
static requiresSystemBlock = true;
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* The Foundry document class this pass writes.
|
|
506
|
+
*
|
|
507
|
+
* Declared by the class that does the writing, so the table pairing a
|
|
508
|
+
* system with its passes is derived from the passes themselves rather than
|
|
509
|
+
* written out a second time — see
|
|
510
|
+
* {@link module:engine/generate.systemCompilers}.
|
|
511
|
+
*
|
|
512
|
+
* @type {string}
|
|
513
|
+
*/
|
|
514
|
+
static documentClass = "Actor";
|
|
515
|
+
|
|
504
516
|
/**
|
|
505
517
|
* The note-type → document-subtype map this pass compiles against.
|
|
506
518
|
*
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -183,13 +183,16 @@ export class BasePackCompiler {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* Whether this pass's document **is** a system's data, and therefore takes
|
|
186
|
-
* only notes that carry
|
|
186
|
+
* only notes that carry the block this pass reads.
|
|
187
187
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* nothing to say about
|
|
192
|
-
* document**: a subtype, and none of the fields the subtype exists
|
|
188
|
+
* The question is asked of {@link BasePackCompiler#system} — the pass's own
|
|
189
|
+
* system, which an Actor or Item pass always has — rather than of the
|
|
190
|
+
* pack's `system:`, which a pack need not declare. A note carrying no such
|
|
191
|
+
* block has nothing to say about that system, so compiling it would emit a
|
|
192
|
+
* **hollow document**: a subtype, and none of the fields the subtype exists
|
|
193
|
+
* for. `harn-ensemble` ships an `actors-hm3` and an `actors-sohl` from one
|
|
194
|
+
* tree, and `sohl-thalorna` ships one `actors` declaring nothing; the rule
|
|
195
|
+
* is the same in both, because it is the pass that reads a block.
|
|
193
196
|
*
|
|
194
197
|
* False by default, because most passes write documents that are not
|
|
195
198
|
* system data at all. A JournalEntry of prose is the same document under
|
|
@@ -428,44 +431,56 @@ export class BasePackCompiler {
|
|
|
428
431
|
);
|
|
429
432
|
}
|
|
430
433
|
|
|
434
|
+
/**
|
|
435
|
+
* The system block this pass reads, where it reads one.
|
|
436
|
+
*
|
|
437
|
+
* `undefined` on the base class, because most passes read none: a
|
|
438
|
+
* JournalEntry, a Macro, a Scene and an Adventure are Foundry's documents
|
|
439
|
+
* rather than any system's. The Actor and Item passes answer with the
|
|
440
|
+
* block their {@link SystemActorCompiler.documentSubtypes} names, which is
|
|
441
|
+
* the same declaration that says which notes they claim and what each
|
|
442
|
+
* becomes.
|
|
443
|
+
*
|
|
444
|
+
* Stated here so {@link BasePackCompiler#eligibleFor} is total: a pass
|
|
445
|
+
* declaring `requiresSystemBlock` without a system reads no block, and a
|
|
446
|
+
* note can carry nothing for it.
|
|
447
|
+
*
|
|
448
|
+
* @returns {string|undefined} The block key, or `undefined`.
|
|
449
|
+
*/
|
|
450
|
+
get system() {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
|
|
431
454
|
/**
|
|
432
455
|
* Whether a claimed, routed note may become this pack's document at all.
|
|
433
456
|
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* documents
|
|
457
|
+
* **A system block is what makes a game document.** A pass whose document
|
|
458
|
+
* *is* a system's data compiles a note only where the note carries the
|
|
459
|
+
* block that pass reads, so a note saying nothing about any system becomes
|
|
460
|
+
* no Actor and no Item anywhere — its prose still compiles into a page, a
|
|
461
|
+
* PDF leaf and a JournalEntry, which are documents no system defines.
|
|
462
|
+
*
|
|
463
|
+
* Asked of this pass's own {@link BasePackCompiler#system}, never of the
|
|
464
|
+
* pack's `system:`. A pack declaring none is a single-system convenience
|
|
465
|
+
* compiled by the fallback pass for its document type, and that pass still
|
|
466
|
+
* reads exactly one block — so the note-side question is the same one
|
|
467
|
+
* whether or not the pack answered it.
|
|
468
|
+
*
|
|
469
|
+
* A note this pass has nothing to compile is **skipped**, counted like any
|
|
470
|
+
* other note the pack does not own. A deliberately unstatted person is the
|
|
471
|
+
* ordinary case, and a block deleted by accident shows in the diff that
|
|
472
|
+
* deleted it; what would otherwise be discarded in silence is the block
|
|
473
|
+
* written for a system no pack of the class serves, and that is reported by
|
|
474
|
+
* {@link module:engine/note-claims.unclaimedNoteFindings} rather than here,
|
|
475
|
+
* where a pass can see only its own half of the configuration.
|
|
439
476
|
*
|
|
440
477
|
* @param {object} fm - The note's frontmatter.
|
|
441
478
|
* @returns {boolean} True when the note may be compiled here; `false` when
|
|
442
|
-
* it
|
|
443
|
-
* skipped as quietly as any other note this pack does not own.
|
|
444
|
-
* @throws {Error} When this pack's system is absent from the note and no
|
|
445
|
-
* other configured system claims it. The error carries a `position` where
|
|
446
|
-
* the note's own file can be read.
|
|
479
|
+
* it carries nothing for this pass's system.
|
|
447
480
|
*/
|
|
448
481
|
eligibleFor(fm) {
|
|
449
|
-
if (!this.constructor.requiresSystemBlock
|
|
450
|
-
|
|
451
|
-
// Another system's pack of this document type will claim it. A
|
|
452
|
-
// note carrying only `hm3:` routes here because this pack is the
|
|
453
|
-
// *default* of its document type, and defaults are declared per type
|
|
454
|
-
// rather than per system — but it is not an incomplete note, it is
|
|
455
|
-
// another pass's. The single-system case is untouched: with no second
|
|
456
|
-
// system declared there is nothing for this to find, and the error
|
|
457
|
-
// below still fires.
|
|
458
|
-
const claimant = (this.router?.systemsOfType?.(this.docType) ?? []).find(
|
|
459
|
-
(system) => system !== this.packSystem && carriesSystemBlock(fm, system),
|
|
460
|
-
);
|
|
461
|
-
if (claimant) return false;
|
|
462
|
-
const label = fm?.name?.full ?? fm?.shortcode ?? fm?.id ?? "this note";
|
|
463
|
-
throw new Error(
|
|
464
|
-
`${label} carries no \`${this.packSystem}:\` block, so it has no ` +
|
|
465
|
-
`${this.packSystem} data to compile — but it routes to pack ` +
|
|
466
|
-
`"${this.packName}", which declares \`system: ${this.packSystem}\`. ` +
|
|
467
|
-
`Add the block, or route the note to a pack of another system.`,
|
|
468
|
-
);
|
|
482
|
+
if (!this.constructor.requiresSystemBlock) return true;
|
|
483
|
+
return carriesSystemBlock(fm, this.system);
|
|
469
484
|
}
|
|
470
485
|
|
|
471
486
|
/**
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
NEVER_PACKED_TYPES,
|
|
66
66
|
DERIVED_PACKED_TYPES,
|
|
67
67
|
} from "./note-claims.mjs";
|
|
68
|
+
import { DEFAULT_DOCUMENT_SUBTYPES, SYSTEM_DOCUMENT_CLASSES } from "./subtype-registry.mjs";
|
|
69
|
+
import { carriesSystemBlock } from "./system-block.mjs";
|
|
68
70
|
import { walkMarkdownTree } from "./helpers.mjs";
|
|
69
71
|
import { resolveNoteId } from "./note-ids.mjs";
|
|
70
72
|
import { compendiumUuid, currentType, packForType, pageUuid } from "./ids.mjs";
|
|
@@ -207,14 +209,30 @@ export function entriesForNote(fm, name, address, body, ctx) {
|
|
|
207
209
|
return undefined;
|
|
208
210
|
}
|
|
209
211
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
type,
|
|
213
|
-
id,
|
|
212
|
+
const docType = routeFm ? packForType(type).docType : "JournalEntry";
|
|
213
|
+
const pack =
|
|
214
214
|
routeFm ?
|
|
215
|
-
packRouter.resolveOrNull(routeFm,
|
|
216
|
-
: packRouter.defaultOf("JournalEntry")
|
|
217
|
-
|
|
215
|
+
packRouter.resolveOrNull(routeFm, docType)
|
|
216
|
+
: packRouter.defaultOf("JournalEntry");
|
|
217
|
+
|
|
218
|
+
// **A system block is what makes a game document**, so a note carrying
|
|
219
|
+
// none compiles into no Actor and no Item — and an address naming one
|
|
220
|
+
// would send every consumer to a document that is not there. The same
|
|
221
|
+
// statement `pack: none` makes about a pack, made about a system: the
|
|
222
|
+
// note keeps its address, its page and its prose journal, and publishes
|
|
223
|
+
// no UUID for the document it does not produce.
|
|
224
|
+
//
|
|
225
|
+
// Which block is asked for is the pack's, because the pack decides the
|
|
226
|
+
// pass: one declaring a `system:` is compiled by that system's pass,
|
|
227
|
+
// and one declaring none by the fallback, which reads a single block of
|
|
228
|
+
// its own.
|
|
229
|
+
if (SYSTEM_DOCUMENT_CLASSES.has(docType)) {
|
|
230
|
+
const system =
|
|
231
|
+
(pack && packRouter.systemOf?.(pack)) || DEFAULT_DOCUMENT_SUBTYPES.system;
|
|
232
|
+
if (!carriesSystemBlock(routeFm, system)) return undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return compendiumUuid(foundryPackageId, type, id, pack);
|
|
218
236
|
};
|
|
219
237
|
|
|
220
238
|
const carriesDoc =
|
package/engine/generate.mjs
CHANGED
|
@@ -102,7 +102,22 @@ const COMPILERS = {
|
|
|
102
102
|
};
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
105
|
+
* Every pass this toolchain ships that writes one system's data.
|
|
106
|
+
*
|
|
107
|
+
* The list a system joins, and the only place it is named. Each class already
|
|
108
|
+
* declares which system it is for (`documentSubtypes`) and which document it
|
|
109
|
+
* writes (`documentClass`), so the pairing is read off the passes rather than
|
|
110
|
+
* spelled a second time beside them — a table keyed by system id is a table
|
|
111
|
+
* free to disagree with the classes it names, and the way it disagrees is
|
|
112
|
+
* silent: a system absent from it took the fallback pass and compiled its packs
|
|
113
|
+
* out of a neighbour's blocks.
|
|
114
|
+
*
|
|
115
|
+
* @type {readonly Function[]}
|
|
116
|
+
*/
|
|
117
|
+
const SYSTEM_PASSES = Object.freeze([Items, Actors, Hm3Items, Hm3Actors]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The system-specific compilers, derived from the passes themselves.
|
|
106
121
|
*
|
|
107
122
|
* A repository feeding two systems declares one Item pack and one Actor pack
|
|
108
123
|
* per system — `harn-ensemble` has `actors-hm3` and `actors-sohl` — and each
|
|
@@ -111,28 +126,84 @@ const COMPILERS = {
|
|
|
111
126
|
* `itemBuilders` lookup already read, so nothing new is declared to make the
|
|
112
127
|
* compiler follow it.
|
|
113
128
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
129
|
+
* @param {readonly Function[]} passes - The system passes to table.
|
|
130
|
+
* @returns {Readonly<Record<string, Readonly<Record<string, Function>>>>}
|
|
131
|
+
* System id → document class → pass.
|
|
132
|
+
* @throws {Error} When a pass declares no system or no document class, which is
|
|
133
|
+
* a pass that cannot be addressed by either question this table answers.
|
|
134
|
+
*/
|
|
135
|
+
export function systemCompilers(passes) {
|
|
136
|
+
/** @type {Record<string, Record<string, Function>>} */
|
|
137
|
+
const table = {};
|
|
138
|
+
for (const pass of passes) {
|
|
139
|
+
const declared =
|
|
140
|
+
/** @type {{documentSubtypes?: {system?: string},
|
|
141
|
+
* documentClass?: string, name: string}} */ (pass);
|
|
142
|
+
const system = declared.documentSubtypes?.system;
|
|
143
|
+
const docType = declared.documentClass;
|
|
144
|
+
if (!system || !docType) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`${declared.name} is registered as a system pass but declares ` +
|
|
147
|
+
`${system ? "no `documentClass`" : "no `documentSubtypes`"} — a pass ` +
|
|
148
|
+
`writing a system's data states both, so which system and which ` +
|
|
149
|
+
`document it answers for is read from the class itself.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
(table[system] ??= {})[docType] = pass;
|
|
153
|
+
}
|
|
154
|
+
return Object.freeze(
|
|
155
|
+
Object.fromEntries(
|
|
156
|
+
Object.entries(table).map(([system, byType]) => [system, Object.freeze(byType)]),
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @type {Readonly<Record<string, Readonly<Record<string, Function>>>>} */
|
|
162
|
+
const SYSTEM_COMPILERS = systemCompilers(SYSTEM_PASSES);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The document classes some system writes its own data into.
|
|
166
|
+
*
|
|
167
|
+
* Derived from {@link SYSTEM_COMPILERS}, so it is the set of classes for which
|
|
168
|
+
* "which system?" is a question with consequences. Every other class has one
|
|
169
|
+
* implementation whatever a pack declares.
|
|
118
170
|
*
|
|
119
|
-
* @type {
|
|
171
|
+
* @type {ReadonlySet<string>}
|
|
120
172
|
*/
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
173
|
+
const SYSTEM_DOCUMENT_CLASSES = Object.freeze(
|
|
174
|
+
new Set(Object.values(SYSTEM_COMPILERS).flatMap((byType) => Object.keys(byType))),
|
|
175
|
+
);
|
|
125
176
|
|
|
126
177
|
/**
|
|
127
178
|
* The compiler class a pack of one document type and one system gets.
|
|
128
179
|
*
|
|
180
|
+
* A pack declaring **no** system takes the fallback pass for its document
|
|
181
|
+
* class, which reads
|
|
182
|
+
* {@link module:engine/subtype-registry.DEFAULT_DOCUMENT_SUBTYPES} — the
|
|
183
|
+
* single-system convenience every tree shipping for one system is built on.
|
|
184
|
+
*
|
|
185
|
+
* A pack declaring a system this toolchain has no pass for is **refused by
|
|
186
|
+
* name**. Falling through to the fallback would compile that pack out of
|
|
187
|
+
* another system's blocks and ship it stamped with the system it asked for,
|
|
188
|
+
* which is the quiet failure the refusal exists to replace.
|
|
189
|
+
*
|
|
129
190
|
* @param {string} docType - The Foundry document type the pack holds.
|
|
130
191
|
* @param {string|null} [system] - The system the pack declares, if any.
|
|
131
192
|
* @returns {Function|undefined} The compiler class, or `undefined` for a
|
|
132
193
|
* document type nothing here compiles — which {@link generatePack} reports
|
|
133
194
|
* rather than defaulting past.
|
|
195
|
+
* @throws {Error} When a system-bearing document class is asked for a system
|
|
196
|
+
* this toolchain ships no pass for.
|
|
134
197
|
*/
|
|
135
198
|
export function compilerFor(docType, system = null) {
|
|
199
|
+
if (system && SYSTEM_DOCUMENT_CLASSES.has(docType) && !SYSTEM_COMPILERS[system]?.[docType]) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`no ${docType} compiler for system "${system}" — this toolchain ships ` +
|
|
202
|
+
`passes for ${Object.keys(SYSTEM_COMPILERS).sort().join(", ")}. Declare a ` +
|
|
203
|
+
`system one of those names, or add the pass that reads a ` +
|
|
204
|
+
`\`${system}:\` block.`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
136
207
|
return (system && SYSTEM_COMPILERS[system]?.[docType]) || COMPILERS[docType];
|
|
137
208
|
}
|
|
138
209
|
|
|
@@ -58,24 +58,23 @@ export function infoboxFor(system) {
|
|
|
58
58
|
* Whether one system compiles a document for one note.
|
|
59
59
|
*
|
|
60
60
|
* This is what a system box's _available_ asserts, and it is the compile's own
|
|
61
|
-
* question rather than a reading of the frontmatter.
|
|
62
|
-
* is not a note a system has nothing for: where the pack compiling its document
|
|
63
|
-
* declares no `system:`, the document is built from `data:` and the field
|
|
64
|
-
* defaults and ships exactly like any other.
|
|
61
|
+
* question rather than a reading of the frontmatter.
|
|
65
62
|
*
|
|
66
63
|
* Three statements answer it, and they are the three the compile itself
|
|
67
64
|
* follows:
|
|
68
65
|
*
|
|
69
|
-
* 1. **The
|
|
66
|
+
* 1. **The note's block.** A system block is what makes a game document, so a
|
|
67
|
+
* note carrying nothing for this system compiles into no document of it
|
|
68
|
+
* anywhere — the rule
|
|
69
|
+
* {@link module:engine/base-compiler.BasePackCompiler#eligibleFor} applies,
|
|
70
|
+
* asked here from outside.
|
|
71
|
+
* 2. **The map** says which document class this system makes of the note's
|
|
70
72
|
* type. No row, no document — and no box either, which is why a caller
|
|
71
73
|
* reaching here already has one.
|
|
72
|
-
*
|
|
73
|
-
* list this build is driven by
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* {@link module:engine/base-compiler.BasePackCompiler#eligibleFor} applies,
|
|
77
|
-
* asked here from outside. Declaring none, it is compiled by the fallback
|
|
78
|
-
* pass, which needs no block and answers for
|
|
74
|
+
* 3. **The router** says which pack that document goes to, read from the pack
|
|
75
|
+
* list this build is driven by, and **that pack's `system:`** decides whose
|
|
76
|
+
* it is. Declaring one, it writes that system's data. Declaring none, it is
|
|
77
|
+
* compiled by the fallback pass, which reads
|
|
79
78
|
* {@link module:engine/subtype-registry.DEFAULT_DOCUMENT_SUBTYPES} alone —
|
|
80
79
|
* so a tree with no HM3 pack ships no HM3 document however a note is
|
|
81
80
|
* written.
|
|
@@ -86,6 +85,8 @@ export function infoboxFor(system) {
|
|
|
86
85
|
* @returns {boolean} True when this system compiles a document for this note.
|
|
87
86
|
*/
|
|
88
87
|
export function compilesSystemDocument(fm, map, router) {
|
|
88
|
+
if (!carriesSystemBlock(fm, map.block)) return false;
|
|
89
|
+
|
|
89
90
|
const row = subtypeRow(map, fm?.type);
|
|
90
91
|
if (!row?.document) return false;
|
|
91
92
|
|
|
@@ -94,7 +95,7 @@ export function compilesSystemDocument(fm, map, router) {
|
|
|
94
95
|
|
|
95
96
|
const packSystem = router.systemOf(packName);
|
|
96
97
|
if (!packSystem) return map.system === DEFAULT_DOCUMENT_SUBTYPES.system;
|
|
97
|
-
return packSystem === map.system
|
|
98
|
+
return packSystem === map.system;
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/**
|
package/engine/item-compiler.mjs
CHANGED
|
@@ -116,6 +116,18 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
116
116
|
*/
|
|
117
117
|
static requiresSystemBlock = true;
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* The Foundry document class this pass writes.
|
|
121
|
+
*
|
|
122
|
+
* Declared by the class that does the writing, so the table pairing a
|
|
123
|
+
* system with its passes is derived from the passes themselves rather than
|
|
124
|
+
* written out a second time — see
|
|
125
|
+
* {@link module:engine/generate.systemCompilers}.
|
|
126
|
+
*
|
|
127
|
+
* @type {string}
|
|
128
|
+
*/
|
|
129
|
+
static documentClass = "Item";
|
|
130
|
+
|
|
119
131
|
/**
|
|
120
132
|
* An Item carries one piece of art — its icon — which this pass writes from
|
|
121
133
|
* the address the note names, falling back to the type's default art.
|
package/engine/note-claims.mjs
CHANGED
|
@@ -94,7 +94,13 @@ import { loadPackConfig } from "./pack-config.mjs";
|
|
|
94
94
|
import { declaresNoPack } from "./pack-router.mjs";
|
|
95
95
|
import { locateFrontmatterKey } from "./retired-fields.mjs";
|
|
96
96
|
import { noteTypesFor, subtypeRow } from "./document-subtypes.mjs";
|
|
97
|
-
import {
|
|
97
|
+
import {
|
|
98
|
+
DEFAULT_DOCUMENT_SUBTYPES,
|
|
99
|
+
KNOWN_DOCUMENT_SUBTYPE_MAPS,
|
|
100
|
+
SHIPPED_SYSTEMS,
|
|
101
|
+
subtypeMapFor,
|
|
102
|
+
} from "./subtype-registry.mjs";
|
|
103
|
+
import { carriesSystemBlock } from "./system-block.mjs";
|
|
98
104
|
import { HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
99
105
|
import { FOLDER_TYPE } from "./folder-notes.mjs";
|
|
100
106
|
import { BUNDLE_TYPE } from "./bundle-notes.mjs";
|
|
@@ -615,7 +621,92 @@ function authoringMessage(type) {
|
|
|
615
621
|
}
|
|
616
622
|
|
|
617
623
|
/**
|
|
618
|
-
*
|
|
624
|
+
* Which pass would read one system's block, given a configuration's packs.
|
|
625
|
+
*
|
|
626
|
+
* A pack declaring `system: hm3` is compiled by the HM3 pass and reads `hm3:`.
|
|
627
|
+
* A pack declaring **no** system is compiled by the fallback pass, which reads
|
|
628
|
+
* {@link module:engine/subtype-registry.DEFAULT_DOCUMENT_SUBTYPES} and nothing
|
|
629
|
+
* else — so a systemless pack answers for one system, not for every system a
|
|
630
|
+
* note might carry. That asymmetry is the whole of this question: the router
|
|
631
|
+
* will happily route an `hm3:` document into a systemless pack, and the pass
|
|
632
|
+
* waiting there reads a different block.
|
|
633
|
+
*
|
|
634
|
+
* A **prebuilt** pack compiles nothing — its JSON is checked in — so it reads
|
|
635
|
+
* no block either.
|
|
636
|
+
*
|
|
637
|
+
* @param {object} config - The resolved build configuration.
|
|
638
|
+
* @param {string} docType - The document class the block would compile into.
|
|
639
|
+
* @param {string} system - The system whose block is in question.
|
|
640
|
+
* @returns {{packs: number, serving: number}} How many non-prebuilt packs of
|
|
641
|
+
* that class the configuration has, and how many of them compile that
|
|
642
|
+
* system's documents.
|
|
643
|
+
*/
|
|
644
|
+
function packsServing(config, docType, system) {
|
|
645
|
+
const packs = (config?.packs ?? []).filter((pack) => !pack?.prebuilt && pack?.type === docType);
|
|
646
|
+
const serving = packs.filter((pack) =>
|
|
647
|
+
pack.system ? pack.system === system : system === DEFAULT_DOCUMENT_SUBTYPES.system,
|
|
648
|
+
);
|
|
649
|
+
return { packs: packs.length, serving: serving.length };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Every system block on one note that no pack in this package will read.
|
|
654
|
+
*
|
|
655
|
+
* **A discarded field is the quiet-failure class the diagnostics exist to
|
|
656
|
+
* close**, and a system block is the largest field a note can write: a whole
|
|
657
|
+
* document's worth of authored data, compiled by nobody and reported by
|
|
658
|
+
* nobody. The claim check beside this one asks whether a note's *type* is
|
|
659
|
+
* claimed; this asks whether its *systems* are.
|
|
660
|
+
*
|
|
661
|
+
* `error`, because the data is lost either way and the author cannot see it
|
|
662
|
+
* happen. It says nothing about a note carrying **no** block — that note is
|
|
663
|
+
* making a statement, and the statement is that nobody rolls dice against this
|
|
664
|
+
* person.
|
|
665
|
+
*
|
|
666
|
+
* Silent, too, where the class has no pack at all: the note's whole document is
|
|
667
|
+
* missing, which {@link partialMessage} already reports with the remedy.
|
|
668
|
+
*
|
|
669
|
+
* @param {object} fm - The note's frontmatter.
|
|
670
|
+
* @param {string} absPath - The note's path, for the finding's position.
|
|
671
|
+
* @param {string} type - The note's current type.
|
|
672
|
+
* @param {object} config - The resolved build configuration.
|
|
673
|
+
* @returns {Array<{file: string, line?: number, column?: number,
|
|
674
|
+
* severity: "error", message: string, type: string}>} One finding per
|
|
675
|
+
* discarded block.
|
|
676
|
+
*/
|
|
677
|
+
function discardedSystemBlockFindings(fm, absPath, type, config) {
|
|
678
|
+
const findings = [];
|
|
679
|
+
for (const system of SHIPPED_SYSTEMS) {
|
|
680
|
+
if (!carriesSystemBlock(fm, system)) continue;
|
|
681
|
+
const docType = subtypeRow(subtypeMapFor(system), type)?.document;
|
|
682
|
+
// This system makes no document of this note type, so its block says
|
|
683
|
+
// nothing that could be discarded — it is read for the note's fields
|
|
684
|
+
// wherever another pass wants them.
|
|
685
|
+
if (!docType) continue;
|
|
686
|
+
const { packs, serving } = packsServing(config, docType, system);
|
|
687
|
+
if (!packs || serving) continue;
|
|
688
|
+
findings.push({
|
|
689
|
+
file: absPath,
|
|
690
|
+
...locateFrontmatterKey(absPath, system, undefined, { topLevel: true }),
|
|
691
|
+
severity: /** @type {"error"} */ ("error"),
|
|
692
|
+
type,
|
|
693
|
+
message:
|
|
694
|
+
`carries a \`${system}:\` block, and no ${docType} pack in this ` +
|
|
695
|
+
`package compiles ${system} documents, so the block is discarded — ` +
|
|
696
|
+
`declare \`system: ${system}\` on one of the \`packs:\`, or remove ` +
|
|
697
|
+
`the block.`,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return findings;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Every note in the content tree that no configured pack would compile, and
|
|
705
|
+
* every system block no configured pack would read.
|
|
706
|
+
*
|
|
707
|
+
* Two questions of one walk, because they are the same question asked of a
|
|
708
|
+
* note's type and of a note's systems, and a note can fail either while passing
|
|
709
|
+
* the other — see {@link discardedSystemBlockFindings}.
|
|
619
710
|
*
|
|
620
711
|
* Read-only: it walks the tree and reports, and writes nothing. Three kinds of
|
|
621
712
|
* note are passed over, each for a stated reason rather than by omission — a
|
|
@@ -672,6 +763,11 @@ export function unclaimedNoteFindings(config = loadPackConfig(), sources, { reco
|
|
|
672
763
|
// lint, which can say what to write instead.
|
|
673
764
|
const current = currentType(type);
|
|
674
765
|
|
|
766
|
+
// Asked first, and asked whatever the type's claim turns out to be: a
|
|
767
|
+
// note whose documents all have packs may still carry a block none of
|
|
768
|
+
// them reads, and that is invisible to every question below.
|
|
769
|
+
findings.push(...discardedSystemBlockFindings(fm, absPath, current, config));
|
|
770
|
+
|
|
675
771
|
// Every document this note produces, against the classes this
|
|
676
772
|
// configuration has a pack for. Three outcomes, and the middle one is
|
|
677
773
|
// the half-claimed one — invisible while the question is only "is anything
|
|
@@ -68,12 +68,15 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS = Object.freeze([
|
|
|
68
68
|
/**
|
|
69
69
|
* The map a pack declaring no `system:` compiles its documents against.
|
|
70
70
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* **The single-system fallback, and not a default any system inherits.** A pack
|
|
72
|
+
* names the system its documents are shaped for, and one declaring none is
|
|
73
|
+
* compiled by the fallback pass for its document type — a single-system
|
|
73
74
|
* package's ordinary arrangement, and the one `sohl-thalorna` ships. That pass
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
75
|
+
* reads this map's block and no other, so a note carrying nothing for this
|
|
76
|
+
* system compiles into no document there; a note carrying another system's
|
|
77
|
+
* block reaches that system's pass or nothing at all, never this one. This is
|
|
78
|
+
* the answer to "a document compiled here belongs to *which* system?" wherever
|
|
79
|
+
* a pack has not said, and it answers nothing about a system that has said.
|
|
77
80
|
*
|
|
78
81
|
* Stated here rather than read off the compiler classes because those are not
|
|
79
82
|
* reachable from a leaf: `engine/item-compiler.mjs` imports the journals pass,
|
|
@@ -86,6 +89,40 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS = Object.freeze([
|
|
|
86
89
|
*/
|
|
87
90
|
export const DEFAULT_DOCUMENT_SUBTYPES = SOHL_DOCUMENT_SUBTYPES;
|
|
88
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Every system this toolchain ships a map for, by id.
|
|
94
|
+
*
|
|
95
|
+
* Derived from {@link KNOWN_DOCUMENT_SUBTYPE_MAPS}, so registering a map is the
|
|
96
|
+
* whole of adding a system to every reader that asks "which systems are there?"
|
|
97
|
+
* — the per-system findings among them.
|
|
98
|
+
*
|
|
99
|
+
* @type {readonly string[]}
|
|
100
|
+
*/
|
|
101
|
+
export const SHIPPED_SYSTEMS = Object.freeze(KNOWN_DOCUMENT_SUBTYPE_MAPS.map((map) => map.system));
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The Foundry document classes a system compiles its own data into.
|
|
105
|
+
*
|
|
106
|
+
* Derived from the shipped maps' rows, so it is the set of classes for which
|
|
107
|
+
* "which system?" is a question with consequences: a pack of one of these
|
|
108
|
+
* classes is compiled by one system's pass reading one system's block, and a
|
|
109
|
+
* note carrying no such block produces nothing there. Every other class — a
|
|
110
|
+
* JournalEntry, a Macro, a Scene, an Adventure — has one implementation
|
|
111
|
+
* whatever a pack declares, because it is Foundry's document rather than any
|
|
112
|
+
* system's.
|
|
113
|
+
*
|
|
114
|
+
* @type {ReadonlySet<string>}
|
|
115
|
+
*/
|
|
116
|
+
export const SYSTEM_DOCUMENT_CLASSES = Object.freeze(
|
|
117
|
+
new Set(
|
|
118
|
+
KNOWN_DOCUMENT_SUBTYPE_MAPS.flatMap((map) =>
|
|
119
|
+
Object.values(map.types)
|
|
120
|
+
.map((row) => row?.document)
|
|
121
|
+
.filter(Boolean),
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
|
|
89
126
|
/**
|
|
90
127
|
* Every note type any shipped map compiles into an **Actor**.
|
|
91
128
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.6.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",
|
|
@@ -199,6 +199,17 @@ export function loadItemsMap(itemsSourceDirs: readonly string[], foreignSourceDi
|
|
|
199
199
|
* becomes embedded items. Nothing above that line is a system's business.
|
|
200
200
|
*/
|
|
201
201
|
export class SystemActorCompiler extends BasePackCompiler {
|
|
202
|
+
/**
|
|
203
|
+
* The Foundry document class this pass writes.
|
|
204
|
+
*
|
|
205
|
+
* Declared by the class that does the writing, so the table pairing a
|
|
206
|
+
* system with its passes is derived from the passes themselves rather than
|
|
207
|
+
* written out a second time — see
|
|
208
|
+
* {@link module:engine/generate.systemCompilers}.
|
|
209
|
+
*
|
|
210
|
+
* @type {string}
|
|
211
|
+
*/
|
|
212
|
+
static documentClass: string;
|
|
202
213
|
/**
|
|
203
214
|
* The note-type → document-subtype map this pass compiles against.
|
|
204
215
|
*
|
|
@@ -82,13 +82,16 @@ export class BasePackCompiler {
|
|
|
82
82
|
static readsPackOutputOf: readonly string[];
|
|
83
83
|
/**
|
|
84
84
|
* Whether this pass's document **is** a system's data, and therefore takes
|
|
85
|
-
* only notes that carry
|
|
85
|
+
* only notes that carry the block this pass reads.
|
|
86
86
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* nothing to say about
|
|
91
|
-
* document**: a subtype, and none of the fields the subtype exists
|
|
87
|
+
* The question is asked of {@link BasePackCompiler#system} — the pass's own
|
|
88
|
+
* system, which an Actor or Item pass always has — rather than of the
|
|
89
|
+
* pack's `system:`, which a pack need not declare. A note carrying no such
|
|
90
|
+
* block has nothing to say about that system, so compiling it would emit a
|
|
91
|
+
* **hollow document**: a subtype, and none of the fields the subtype exists
|
|
92
|
+
* for. `harn-ensemble` ships an `actors-hm3` and an `actors-sohl` from one
|
|
93
|
+
* tree, and `sohl-thalorna` ships one `actors` declaring nothing; the rule
|
|
94
|
+
* is the same in both, because it is the pass that reads a block.
|
|
92
95
|
*
|
|
93
96
|
* False by default, because most passes write documents that are not
|
|
94
97
|
* system data at all. A JournalEntry of prose is the same document under
|
|
@@ -265,22 +268,49 @@ export class BasePackCompiler {
|
|
|
265
268
|
* routes to no pack at all — a build failure, never a silent drop.
|
|
266
269
|
*/
|
|
267
270
|
routesHere(fm: object): boolean;
|
|
271
|
+
/**
|
|
272
|
+
* The system block this pass reads, where it reads one.
|
|
273
|
+
*
|
|
274
|
+
* `undefined` on the base class, because most passes read none: a
|
|
275
|
+
* JournalEntry, a Macro, a Scene and an Adventure are Foundry's documents
|
|
276
|
+
* rather than any system's. The Actor and Item passes answer with the
|
|
277
|
+
* block their {@link SystemActorCompiler.documentSubtypes} names, which is
|
|
278
|
+
* the same declaration that says which notes they claim and what each
|
|
279
|
+
* becomes.
|
|
280
|
+
*
|
|
281
|
+
* Stated here so {@link BasePackCompiler#eligibleFor} is total: a pass
|
|
282
|
+
* declaring `requiresSystemBlock` without a system reads no block, and a
|
|
283
|
+
* note can carry nothing for it.
|
|
284
|
+
*
|
|
285
|
+
* @returns {string|undefined} The block key, or `undefined`.
|
|
286
|
+
*/
|
|
287
|
+
get system(): string | undefined;
|
|
268
288
|
/**
|
|
269
289
|
* Whether a claimed, routed note may become this pack's document at all.
|
|
270
290
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* documents
|
|
291
|
+
* **A system block is what makes a game document.** A pass whose document
|
|
292
|
+
* *is* a system's data compiles a note only where the note carries the
|
|
293
|
+
* block that pass reads, so a note saying nothing about any system becomes
|
|
294
|
+
* no Actor and no Item anywhere — its prose still compiles into a page, a
|
|
295
|
+
* PDF leaf and a JournalEntry, which are documents no system defines.
|
|
296
|
+
*
|
|
297
|
+
* Asked of this pass's own {@link BasePackCompiler#system}, never of the
|
|
298
|
+
* pack's `system:`. A pack declaring none is a single-system convenience
|
|
299
|
+
* compiled by the fallback pass for its document type, and that pass still
|
|
300
|
+
* reads exactly one block — so the note-side question is the same one
|
|
301
|
+
* whether or not the pack answered it.
|
|
302
|
+
*
|
|
303
|
+
* A note this pass has nothing to compile is **skipped**, counted like any
|
|
304
|
+
* other note the pack does not own. A deliberately unstatted person is the
|
|
305
|
+
* ordinary case, and a block deleted by accident shows in the diff that
|
|
306
|
+
* deleted it; what would otherwise be discarded in silence is the block
|
|
307
|
+
* written for a system no pack of the class serves, and that is reported by
|
|
308
|
+
* {@link module:engine/note-claims.unclaimedNoteFindings} rather than here,
|
|
309
|
+
* where a pass can see only its own half of the configuration.
|
|
276
310
|
*
|
|
277
311
|
* @param {object} fm - The note's frontmatter.
|
|
278
312
|
* @returns {boolean} True when the note may be compiled here; `false` when
|
|
279
|
-
* it
|
|
280
|
-
* skipped as quietly as any other note this pack does not own.
|
|
281
|
-
* @throws {Error} When this pack's system is absent from the note and no
|
|
282
|
-
* other configured system claims it. The error carries a `position` where
|
|
283
|
-
* the note's own file can be read.
|
|
313
|
+
* it carries nothing for this pass's system.
|
|
284
314
|
*/
|
|
285
315
|
eligibleFor(fm: object): boolean;
|
|
286
316
|
/**
|
|
@@ -1,11 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The system-specific compilers, derived from the passes themselves.
|
|
3
|
+
*
|
|
4
|
+
* A repository feeding two systems declares one Item pack and one Actor pack
|
|
5
|
+
* per system — `harn-ensemble` has `actors-hm3` and `actors-sohl` — and each
|
|
6
|
+
* pack's `system:` is what says whose data model its documents are shaped for.
|
|
7
|
+
* That is the same field the `_stats` stamp, the item-catalogue scope and the
|
|
8
|
+
* `itemBuilders` lookup already read, so nothing new is declared to make the
|
|
9
|
+
* compiler follow it.
|
|
10
|
+
*
|
|
11
|
+
* @param {readonly Function[]} passes - The system passes to table.
|
|
12
|
+
* @returns {Readonly<Record<string, Readonly<Record<string, Function>>>>}
|
|
13
|
+
* System id → document class → pass.
|
|
14
|
+
* @throws {Error} When a pass declares no system or no document class, which is
|
|
15
|
+
* a pass that cannot be addressed by either question this table answers.
|
|
16
|
+
*/
|
|
17
|
+
export function systemCompilers(passes: readonly Function[]): Readonly<Record<string, Readonly<Record<string, Function>>>>;
|
|
1
18
|
/**
|
|
2
19
|
* The compiler class a pack of one document type and one system gets.
|
|
3
20
|
*
|
|
21
|
+
* A pack declaring **no** system takes the fallback pass for its document
|
|
22
|
+
* class, which reads
|
|
23
|
+
* {@link module:engine/subtype-registry.DEFAULT_DOCUMENT_SUBTYPES} — the
|
|
24
|
+
* single-system convenience every tree shipping for one system is built on.
|
|
25
|
+
*
|
|
26
|
+
* A pack declaring a system this toolchain has no pass for is **refused by
|
|
27
|
+
* name**. Falling through to the fallback would compile that pack out of
|
|
28
|
+
* another system's blocks and ship it stamped with the system it asked for,
|
|
29
|
+
* which is the quiet failure the refusal exists to replace.
|
|
30
|
+
*
|
|
4
31
|
* @param {string} docType - The Foundry document type the pack holds.
|
|
5
32
|
* @param {string|null} [system] - The system the pack declares, if any.
|
|
6
33
|
* @returns {Function|undefined} The compiler class, or `undefined` for a
|
|
7
34
|
* document type nothing here compiles — which {@link generatePack} reports
|
|
8
35
|
* rather than defaulting past.
|
|
36
|
+
* @throws {Error} When a system-bearing document class is asked for a system
|
|
37
|
+
* this toolchain ships no pass for.
|
|
9
38
|
*/
|
|
10
39
|
export function compilerFor(docType: string, system?: string | null): Function | undefined;
|
|
11
40
|
/**
|
|
@@ -10,24 +10,23 @@ export function infoboxFor(system: string | undefined): object | undefined;
|
|
|
10
10
|
* Whether one system compiles a document for one note.
|
|
11
11
|
*
|
|
12
12
|
* This is what a system box's _available_ asserts, and it is the compile's own
|
|
13
|
-
* question rather than a reading of the frontmatter.
|
|
14
|
-
* is not a note a system has nothing for: where the pack compiling its document
|
|
15
|
-
* declares no `system:`, the document is built from `data:` and the field
|
|
16
|
-
* defaults and ships exactly like any other.
|
|
13
|
+
* question rather than a reading of the frontmatter.
|
|
17
14
|
*
|
|
18
15
|
* Three statements answer it, and they are the three the compile itself
|
|
19
16
|
* follows:
|
|
20
17
|
*
|
|
21
|
-
* 1. **The
|
|
18
|
+
* 1. **The note's block.** A system block is what makes a game document, so a
|
|
19
|
+
* note carrying nothing for this system compiles into no document of it
|
|
20
|
+
* anywhere — the rule
|
|
21
|
+
* {@link module:engine/base-compiler.BasePackCompiler#eligibleFor} applies,
|
|
22
|
+
* asked here from outside.
|
|
23
|
+
* 2. **The map** says which document class this system makes of the note's
|
|
22
24
|
* type. No row, no document — and no box either, which is why a caller
|
|
23
25
|
* reaching here already has one.
|
|
24
|
-
*
|
|
25
|
-
* list this build is driven by
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* {@link module:engine/base-compiler.BasePackCompiler#eligibleFor} applies,
|
|
29
|
-
* asked here from outside. Declaring none, it is compiled by the fallback
|
|
30
|
-
* pass, which needs no block and answers for
|
|
26
|
+
* 3. **The router** says which pack that document goes to, read from the pack
|
|
27
|
+
* list this build is driven by, and **that pack's `system:`** decides whose
|
|
28
|
+
* it is. Declaring one, it writes that system's data. Declaring none, it is
|
|
29
|
+
* compiled by the fallback pass, which reads
|
|
31
30
|
* {@link module:engine/subtype-registry.DEFAULT_DOCUMENT_SUBTYPES} alone —
|
|
32
31
|
* so a tree with no HM3 pack ships no HM3 document however a note is
|
|
33
32
|
* written.
|
|
@@ -29,6 +29,17 @@ export function itemDescription(markdown: string, fm: object, name: string): str
|
|
|
29
29
|
* a configuration one.
|
|
30
30
|
*/
|
|
31
31
|
export class SystemItemCompiler extends BasePackCompiler {
|
|
32
|
+
/**
|
|
33
|
+
* The Foundry document class this pass writes.
|
|
34
|
+
*
|
|
35
|
+
* Declared by the class that does the writing, so the table pairing a
|
|
36
|
+
* system with its passes is derived from the passes themselves rather than
|
|
37
|
+
* written out a second time — see
|
|
38
|
+
* {@link module:engine/generate.systemCompilers}.
|
|
39
|
+
*
|
|
40
|
+
* @type {string}
|
|
41
|
+
*/
|
|
42
|
+
static documentClass: string;
|
|
32
43
|
/**
|
|
33
44
|
* The note-type → document-subtype map this pass compiles against.
|
|
34
45
|
*
|
|
@@ -102,7 +102,12 @@ export function claimedNoteTypes(config?: object, sources?: ClaimSources): Reado
|
|
|
102
102
|
*/
|
|
103
103
|
export function noteTypeVocabulary(sources?: ClaimSources): ReadonlySet<string>;
|
|
104
104
|
/**
|
|
105
|
-
* Every note in the content tree that no configured pack would compile
|
|
105
|
+
* Every note in the content tree that no configured pack would compile, and
|
|
106
|
+
* every system block no configured pack would read.
|
|
107
|
+
*
|
|
108
|
+
* Two questions of one walk, because they are the same question asked of a
|
|
109
|
+
* note's type and of a note's systems, and a note can fail either while passing
|
|
110
|
+
* the other — see {@link discardedSystemBlockFindings}.
|
|
106
111
|
*
|
|
107
112
|
* Read-only: it walks the tree and reports, and writes nothing. Three kinds of
|
|
108
113
|
* note are passed over, each for a stated reason rather than by omission — a
|
|
@@ -50,12 +50,15 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.m
|
|
|
50
50
|
/**
|
|
51
51
|
* The map a pack declaring no `system:` compiles its documents against.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* **The single-system fallback, and not a default any system inherits.** A pack
|
|
54
|
+
* names the system its documents are shaped for, and one declaring none is
|
|
55
|
+
* compiled by the fallback pass for its document type — a single-system
|
|
55
56
|
* package's ordinary arrangement, and the one `sohl-thalorna` ships. That pass
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
57
|
+
* reads this map's block and no other, so a note carrying nothing for this
|
|
58
|
+
* system compiles into no document there; a note carrying another system's
|
|
59
|
+
* block reaches that system's pass or nothing at all, never this one. This is
|
|
60
|
+
* the answer to "a document compiled here belongs to *which* system?" wherever
|
|
61
|
+
* a pack has not said, and it answers nothing about a system that has said.
|
|
59
62
|
*
|
|
60
63
|
* Stated here rather than read off the compiler classes because those are not
|
|
61
64
|
* reachable from a leaf: `engine/item-compiler.mjs` imports the journals pass,
|
|
@@ -67,6 +70,30 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.m
|
|
|
67
70
|
* @type {import("./document-subtypes.mjs").DocumentSubtypeMap}
|
|
68
71
|
*/
|
|
69
72
|
export const DEFAULT_DOCUMENT_SUBTYPES: import("./document-subtypes.mjs").DocumentSubtypeMap;
|
|
73
|
+
/**
|
|
74
|
+
* Every system this toolchain ships a map for, by id.
|
|
75
|
+
*
|
|
76
|
+
* Derived from {@link KNOWN_DOCUMENT_SUBTYPE_MAPS}, so registering a map is the
|
|
77
|
+
* whole of adding a system to every reader that asks "which systems are there?"
|
|
78
|
+
* — the per-system findings among them.
|
|
79
|
+
*
|
|
80
|
+
* @type {readonly string[]}
|
|
81
|
+
*/
|
|
82
|
+
export const SHIPPED_SYSTEMS: readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
* The Foundry document classes a system compiles its own data into.
|
|
85
|
+
*
|
|
86
|
+
* Derived from the shipped maps' rows, so it is the set of classes for which
|
|
87
|
+
* "which system?" is a question with consequences: a pack of one of these
|
|
88
|
+
* classes is compiled by one system's pass reading one system's block, and a
|
|
89
|
+
* note carrying no such block produces nothing there. Every other class — a
|
|
90
|
+
* JournalEntry, a Macro, a Scene, an Adventure — has one implementation
|
|
91
|
+
* whatever a pack declares, because it is Foundry's document rather than any
|
|
92
|
+
* system's.
|
|
93
|
+
*
|
|
94
|
+
* @type {ReadonlySet<string>}
|
|
95
|
+
*/
|
|
96
|
+
export const SYSTEM_DOCUMENT_CLASSES: ReadonlySet<string>;
|
|
70
97
|
/**
|
|
71
98
|
* Every note type any shipped map compiles into an **Actor**.
|
|
72
99
|
*
|