@heroiclands/package-build 20.3.0 → 20.4.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 +97 -0
- package/CONTENT.md +111 -92
- package/README.md +2 -9
- package/bin/content-build.mjs +70 -61
- package/bin/package-build.mjs +2 -2
- package/config.mjs +6 -8
- package/content-config.mjs +187 -64
- package/docs/content-format.md +57 -68
- package/e2e.mjs +4 -4
- package/engine/actor-compiler.mjs +36 -36
- package/engine/address-charset.mjs +6 -6
- package/engine/address-diff.mjs +12 -11
- package/engine/anchored-sections.mjs +1 -1
- package/engine/anchors.mjs +1 -1
- package/engine/base-compiler.mjs +36 -38
- package/engine/bundle-notes.mjs +4 -4
- package/engine/bundles.mjs +8 -8
- package/engine/code-fences.mjs +3 -3
- package/engine/compendiums.mjs +8 -8
- package/engine/compile-corpus.mjs +4 -5
- package/engine/content-address.mjs +15 -15
- package/engine/content-charset.mjs +1 -1
- package/engine/content-format-check.mjs +24 -25
- package/engine/content-format.mjs +8 -8
- package/engine/content-html.mjs +202 -0
- package/engine/content-icons.mjs +225 -98
- package/engine/content-index.mjs +17 -17
- package/engine/content-links.mjs +30 -30
- package/engine/content-lint.mjs +20 -24
- package/engine/content-package.mjs +7 -9
- package/engine/content-slug.mjs +2 -2
- package/engine/content-tables.mjs +7 -8
- package/engine/content-tree.mjs +1 -1
- package/engine/derived-fields.mjs +174 -0
- package/engine/diagnostics.mjs +3 -4
- package/engine/document-subtypes.mjs +6 -7
- package/engine/field-reference.mjs +8 -8
- package/engine/field-spec.mjs +29 -30
- package/engine/folder-notes.mjs +14 -14
- package/engine/foreign-catalog.mjs +8 -9
- package/engine/foundry-entries.mjs +19 -20
- package/engine/frontmatter-lint.mjs +64 -69
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +39 -39
- package/engine/helpers.mjs +63 -54
- package/engine/homepage.mjs +20 -22
- package/engine/ids.mjs +15 -15
- package/engine/index-records.mjs +4 -4
- package/engine/index.mjs +14 -8
- package/engine/item-compiler.mjs +54 -29
- package/engine/item-docs.mjs +11 -13
- package/engine/item-registry.mjs +13 -13
- package/engine/journals.mjs +15 -15
- package/engine/macros.mjs +3 -3
- package/engine/map-notes.mjs +6 -6
- package/engine/metadata-index.mjs +5 -5
- package/engine/note-claims.mjs +36 -34
- package/engine/note-ids.mjs +5 -5
- package/engine/note-package.mjs +4 -4
- package/engine/note-renames.mjs +3 -3
- package/engine/note-schemas.mjs +3 -3
- package/engine/note-vocabulary.mjs +27 -26
- package/engine/pack-config.mjs +13 -13
- package/engine/pack-router.mjs +0 -0
- package/engine/prose-config.mjs +9 -9
- package/engine/prose-lint.mjs +12 -13
- package/engine/region-events.mjs +2 -2
- package/engine/retired-fields.mjs +24 -22
- package/engine/runtime-only-fields.mjs +1 -1
- package/engine/scene-levels.mjs +1 -1
- package/engine/scenes.mjs +19 -20
- package/engine/schema-check.mjs +20 -20
- package/engine/schema-extract.mjs +1 -1
- package/engine/site-build.mjs +45 -47
- package/engine/site-index.mjs +17 -18
- package/engine/sql-tables.mjs +21 -13
- package/engine/subtype-registry.mjs +5 -4
- package/engine/system-block.mjs +18 -19
- package/engine/systems.mjs +2 -2
- package/engine/web-wikilinks.mjs +35 -35
- package/engine/wikilink-syntax.mjs +16 -16
- package/engine/wikilinks.mjs +39 -39
- package/hm3/actors.mjs +22 -22
- package/hm3/document-subtypes.mjs +5 -5
- package/hm3/index.mjs +2 -2
- package/hm3/item-builders.mjs +1 -1
- package/hm3/item-fields.mjs +3 -3
- package/hm3/items.mjs +20 -8
- package/hm3/template-priority.mjs +2 -2
- package/lang.mjs +3 -3
- package/manifest.mjs +17 -20
- package/package.json +1 -2
- package/release.mjs +3 -3
- package/sohl/actors.mjs +28 -28
- package/sohl/affiliation-standings.mjs +2 -2
- package/sohl/being-info.mjs +5 -5
- package/sohl/default-item-art.mjs +5 -5
- package/sohl/document-subtypes.mjs +5 -5
- package/sohl/index.mjs +3 -3
- package/sohl/item-builders.mjs +7 -7
- package/sohl/item-fields.mjs +8 -9
- package/sohl/items.mjs +20 -6
- package/sohl/kb-passes.mjs +5 -5
- package/sohl/note-schemas.mjs +7 -7
- package/sohl/skill-base.mjs +1 -1
- package/types/content-config.d.mts +25 -24
- package/types/e2e.d.mts +3 -3
- package/types/engine/actor-compiler.d.mts +14 -13
- package/types/engine/address-charset.d.mts +6 -6
- package/types/engine/address-diff.d.mts +3 -3
- package/types/engine/base-compiler.d.mts +17 -19
- package/types/engine/bundle-notes.d.mts +3 -3
- package/types/engine/bundles.d.mts +1 -1
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/compendiums.d.mts +3 -3
- package/types/engine/compile-corpus.d.mts +1 -1
- package/types/engine/content-address.d.mts +11 -11
- package/types/engine/content-format-check.d.mts +6 -6
- package/types/engine/content-format.d.mts +2 -2
- package/types/engine/content-html.d.mts +78 -0
- package/types/engine/content-icons.d.mts +150 -62
- package/types/engine/content-index.d.mts +2 -2
- package/types/engine/content-links.d.mts +8 -8
- package/types/engine/content-lint.d.mts +2 -2
- package/types/engine/content-package.d.mts +6 -8
- package/types/engine/derived-fields.d.mts +101 -0
- package/types/engine/diagnostics.d.mts +2 -2
- package/types/engine/document-subtypes.d.mts +3 -3
- package/types/engine/field-spec.d.mts +37 -39
- package/types/engine/folder-notes.d.mts +4 -5
- package/types/engine/foreign-catalog.d.mts +3 -3
- package/types/engine/foundry-entries.d.mts +6 -7
- package/types/engine/frontmatter-lint.d.mts +7 -7
- package/types/engine/frontmatter.d.mts +7 -7
- package/types/engine/generate.d.mts +7 -7
- package/types/engine/helpers.d.mts +25 -27
- package/types/engine/homepage.d.mts +16 -18
- package/types/engine/ids.d.mts +13 -13
- package/types/engine/index-records.d.mts +3 -3
- package/types/engine/index.d.mts +2 -0
- package/types/engine/item-compiler.d.mts +21 -5
- package/types/engine/item-docs.d.mts +2 -2
- package/types/engine/item-registry.d.mts +6 -6
- package/types/engine/journals.d.mts +3 -3
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/metadata-index.d.mts +4 -4
- package/types/engine/note-claims.d.mts +16 -15
- package/types/engine/note-ids.d.mts +4 -4
- package/types/engine/note-package.d.mts +1 -1
- package/types/engine/note-renames.d.mts +3 -3
- package/types/engine/note-vocabulary.d.mts +9 -8
- package/types/engine/pack-config.d.mts +3 -3
- package/types/engine/pack-router.d.mts +1 -1
- package/types/engine/prose-config.d.mts +9 -9
- package/types/engine/prose-lint.d.mts +3 -4
- package/types/engine/region-events.d.mts +2 -2
- package/types/engine/retired-fields.d.mts +10 -9
- package/types/engine/schema-check.d.mts +11 -11
- package/types/engine/site-build.d.mts +18 -19
- package/types/engine/site-index.d.mts +3 -4
- package/types/engine/sql-tables.d.mts +11 -5
- package/types/engine/subtype-registry.d.mts +3 -3
- package/types/engine/system-block.d.mts +3 -3
- package/types/engine/web-wikilinks.d.mts +7 -7
- package/types/engine/wikilink-syntax.d.mts +17 -17
- package/types/engine/wikilinks.d.mts +13 -12
- package/types/hm3/actors.d.mts +1 -1
- package/types/hm3/document-subtypes.d.mts +1 -1
- package/types/hm3/items.d.mts +1 -2
- package/types/hm3/template-priority.d.mts +1 -1
- package/types/manifest.d.mts +8 -8
- package/types/sohl/actors.d.mts +2 -2
- package/types/sohl/affiliation-standings.d.mts +2 -2
- package/types/sohl/being-info.d.mts +2 -2
- package/types/sohl/document-subtypes.d.mts +1 -1
- package/types/sohl/items.d.mts +1 -2
- package/types/sohl/note-schemas.d.mts +1 -1
- package/MIGRATING.md +0 -608
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Checking a note's **frontmatter** against the schema its `type` declares
|
|
16
|
-
* (#19).
|
|
15
|
+
* Checking a note's **frontmatter** against the schema its `type` declares.
|
|
17
16
|
*
|
|
18
17
|
* Until now nothing did. A note's type decides which properties are required,
|
|
19
18
|
* what shape each value has, and which name another note — but that knowledge
|
|
@@ -43,7 +42,7 @@
|
|
|
43
42
|
* The two rules that are not schema-driven sit on the *note format* side of
|
|
44
43
|
* that line, which is why they are here and not in `sohl/`: the retired
|
|
45
44
|
* top-level fields, and the address-bearing fields a `type: homepage` note
|
|
46
|
-
* refuses
|
|
45
|
+
* refuses. Each supplies its own message from the module that owns the
|
|
47
46
|
* knowledge — `retired-fields.mjs` and `homepage.mjs` — and this module only
|
|
48
47
|
* locates it in the file.
|
|
49
48
|
*
|
|
@@ -69,7 +68,7 @@ import { RETIRED_TYPES, RENAMED_TYPES, currentType, renamedTypeMessage } from ".
|
|
|
69
68
|
import { isAddressSegment } from "./address-charset.mjs";
|
|
70
69
|
// The one place the "every pack not named" key is spelled. Imported rather
|
|
71
70
|
// than repeated, because a linter holding its own copy of what the compiler
|
|
72
|
-
// reads is exactly the disagreement
|
|
71
|
+
// reads is exactly the disagreement to avoid.
|
|
73
72
|
import { DEFAULT_PARENT } from "./folder-notes.mjs";
|
|
74
73
|
import { declaredTags, subTypeCharsetMessage, typeCharsetMessage } from "./note-vocabulary.mjs";
|
|
75
74
|
import {
|
|
@@ -95,7 +94,7 @@ import {
|
|
|
95
94
|
*
|
|
96
95
|
* - `folder` — the compendium folder the document is filed in.
|
|
97
96
|
* - `pack` — which compendium of the note's document type receives it, for a
|
|
98
|
-
* repository shipping several
|
|
97
|
+
* repository shipping several.
|
|
99
98
|
* - `archetype` — the archetype flag, read for any note by the walk itself.
|
|
100
99
|
* - `kbcat` — the knowledgebase category a note is grouped under.
|
|
101
100
|
*
|
|
@@ -162,13 +161,13 @@ export const DEFAULT_SYSTEM_BLOCKS = Object.freeze({
|
|
|
162
161
|
*/
|
|
163
162
|
|
|
164
163
|
/**
|
|
165
|
-
* Every system a configuration says its tree carries
|
|
164
|
+
* Every system a configuration says its tree carries.
|
|
166
165
|
*
|
|
167
166
|
* **Which systems a package ships for is already declared**, in three places
|
|
168
167
|
* that answer different questions, so this reads all three rather than asking a
|
|
169
168
|
* new one:
|
|
170
169
|
*
|
|
171
|
-
* - `systems:`
|
|
170
|
+
* - `systems:` declares them without requiring one, which is how a
|
|
172
171
|
* package ships for several;
|
|
173
172
|
* - a **pack's** `system:` is the same statement made per pack, and it is the
|
|
174
173
|
* one some trees make: `harn-ensemble` declares an `actors-sohl` and an
|
|
@@ -204,7 +203,7 @@ export function declaredSystems(config) {
|
|
|
204
203
|
|
|
205
204
|
/**
|
|
206
205
|
* The system blocks a configuration says its tree carries, and what each
|
|
207
|
-
* accepts
|
|
206
|
+
* accepts.
|
|
208
207
|
*
|
|
209
208
|
* The lint checks the blocks its caller names, and for as long as there was one
|
|
210
209
|
* system the only caller named none — so every tree took the `sohl:` of
|
|
@@ -406,7 +405,7 @@ function dataBlock(fm) {
|
|
|
406
405
|
|
|
407
406
|
/**
|
|
408
407
|
* Check a note's `data:` container against the closed vocabulary its type
|
|
409
|
-
* declares
|
|
408
|
+
* declares.
|
|
410
409
|
*
|
|
411
410
|
* Unlike the top level, which is passed through to the published page and so
|
|
412
411
|
* cannot be refused, `data:` holds the type-specific facts about the subject
|
|
@@ -526,7 +525,7 @@ function checkKeyedMap(note, { field, segments, entries, raw, packs }) {
|
|
|
526
525
|
|
|
527
526
|
// `default` is the map's own key for "every pack not named", not a
|
|
528
527
|
// pack — spelled out rather than left as an absent key, so a map
|
|
529
|
-
// stating only exceptions still reads as a complete answer
|
|
528
|
+
// stating only exceptions still reads as a complete answer.
|
|
530
529
|
if (known && key !== DEFAULT_PARENT && !known.has(key)) {
|
|
531
530
|
const guess = nearest(key, known);
|
|
532
531
|
findings.push({
|
|
@@ -563,38 +562,36 @@ function checkKeyedMap(note, { field, segments, entries, raw, packs }) {
|
|
|
563
562
|
}
|
|
564
563
|
|
|
565
564
|
/**
|
|
566
|
-
* Check a note's top-level `subType` against the values its type declares
|
|
567
|
-
* (#128).
|
|
565
|
+
* Check a note's top-level `subType` against the values its type declares.
|
|
568
566
|
*
|
|
569
567
|
* `subType` stays at the top level — it is what each system's map reads to
|
|
570
568
|
* derive a document type, so it describes the note rather than the subject —
|
|
571
569
|
* but it is not open like the rest of that region: a type either declares a
|
|
572
570
|
* `subType` or does not, and a type that does declares its values.
|
|
573
571
|
*
|
|
574
|
-
* **It is a genre, and only a genre.**
|
|
575
|
-
* `README.md`
|
|
576
|
-
*
|
|
577
|
-
* exist
|
|
572
|
+
* **It is a genre, and only a genre.** A second reading of the field — a
|
|
573
|
+
* `README.md` as its section's landing page, whose `subType` is the segment it
|
|
574
|
+
* lands at — would mean checking the value against the sections that could
|
|
575
|
+
* exist (every content type, plus whatever a repository configured) rather
|
|
578
576
|
* than against the genres its type declares. Two vocabularies in one field is
|
|
579
|
-
*
|
|
580
|
-
*
|
|
577
|
+
* the cause, and it is removed rather than the symptom: a section is a Hugo
|
|
578
|
+
* directory the note format does not
|
|
581
579
|
* carry, and a page introducing a type is an ordinary note addressed
|
|
582
580
|
* `doc-<type>`. So the closed list answers for every note, whatever it is
|
|
583
581
|
* called, and `rules`, `userguide`, `reference` mean three genres and nothing
|
|
584
582
|
* else.
|
|
585
583
|
*
|
|
586
|
-
* **Two checks, in this order** — the charset, then the closed set
|
|
587
|
-
*
|
|
584
|
+
* **Two checks, in this order** — the charset, then the closed set. The
|
|
585
|
+
* charset is first because it is the more general statement about
|
|
588
586
|
* the same value: a value outside `^[A-Za-z0-9]+$` is refused whatever the type
|
|
589
587
|
* declares, and only once it is a well-formed term is the type's own list the
|
|
590
588
|
* reason to refuse it.
|
|
591
589
|
*
|
|
592
|
-
* There
|
|
593
|
-
* `user-guide` as a warning naming `userguide
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
* reason that always applied — it contains a hyphen (#210).
|
|
590
|
+
* There is deliberately no third, retired-spelling check ahead of them
|
|
591
|
+
* accepting `user-guide` as a warning naming `userguide`. Every consumer tree
|
|
592
|
+
* has swept, so it would guard nothing: the old spelling falls through to the
|
|
593
|
+
* charset check, which refuses it for the reason that always applied — it
|
|
594
|
+
* contains a hyphen.
|
|
598
595
|
*
|
|
599
596
|
* @param {object} note - The note.
|
|
600
597
|
* @param {object} opts
|
|
@@ -658,7 +655,7 @@ function checkSubType(note, { type, entry }) {
|
|
|
658
655
|
}
|
|
659
656
|
|
|
660
657
|
/**
|
|
661
|
-
* Check a note's `tags` for near misses against the tags that classify
|
|
658
|
+
* Check a note's `tags` for near misses against the tags that classify.
|
|
662
659
|
*
|
|
663
660
|
* `tags:` is top-level and the top level is open, so an unrecognised tag is
|
|
664
661
|
* **not** a finding: a theme, a region or a working state is the author's own
|
|
@@ -717,13 +714,13 @@ function checkTags(note, { type }) {
|
|
|
717
714
|
* empty value belongs to the translator, not to whichever key happens to be
|
|
718
715
|
* more common. Eleven `sohl-kethira-basic` beings write `portrait: ""` and no
|
|
719
716
|
* note in any tree writes `img: ""` on a being; a check keyed on `img` alone
|
|
720
|
-
* would have called that tree clean
|
|
717
|
+
* would have called that tree clean.
|
|
721
718
|
*
|
|
722
719
|
* **Each carries where it is authored**, because the two no longer agree. The
|
|
723
720
|
* specification puts an actor's portrait under `data:` and leaves its token art
|
|
724
721
|
* at the note's top level, so `portrait` has a third position to read and `img`
|
|
725
722
|
* does not — and a check that read only the two they share would pass a
|
|
726
|
-
* `data.portrait: ""` it could not see
|
|
723
|
+
* `data.portrait: ""` it could not see.
|
|
727
724
|
*
|
|
728
725
|
* @type {readonly {key: string, inData: boolean}[]}
|
|
729
726
|
*/
|
|
@@ -749,7 +746,7 @@ const ART_FIELDS = Object.freeze([
|
|
|
749
746
|
* "Ajaw", "Warden". Twenty-eight `sohl-kethira-basic` affiliations author
|
|
750
747
|
* `sohl.title: ""` — an office with no style of address, which is ordinary —
|
|
751
748
|
* and every one of them was reported as publishing a page with no heading. None
|
|
752
|
-
* of them does; their pages take `name.full` exactly as intended
|
|
749
|
+
* of them does; their pages take `name.full` exactly as intended.
|
|
753
750
|
*
|
|
754
751
|
* Keyed on the **in-block** key — `legacyKey` where a field declares one, and
|
|
755
752
|
* its first segment where that is dotted — because that is the position a note
|
|
@@ -764,13 +761,13 @@ const ART_FIELDS = Object.freeze([
|
|
|
764
761
|
*
|
|
765
762
|
* The first segment of each field's in-block key: `impact.die` is authored as
|
|
766
763
|
* `impact`, and a field whose shared source moved under `data:` is authored at
|
|
767
|
-
* the `legacyKey` it declares rather than at its dotted name
|
|
764
|
+
* the `legacyKey` it declares rather than at its dotted name. Keying on
|
|
768
765
|
* the name instead would report `sohl.species` as a property no `being` has,
|
|
769
766
|
* against exactly the notes the sweep has not reached yet.
|
|
770
767
|
*
|
|
771
768
|
* Written once and read twice: the note type's own declaration answers for the
|
|
772
769
|
* system whose vocabulary the caller's `schemas` are, and a second system's
|
|
773
|
-
* registry answers for its block
|
|
770
|
+
* registry answers for its block. Two derivations of one thing would be
|
|
774
771
|
* free to disagree about which position a note authors.
|
|
775
772
|
*
|
|
776
773
|
* @param {readonly object[]|null|undefined} schema - A type's declarations.
|
|
@@ -798,12 +795,11 @@ function collidingBlockKeys(schema) {
|
|
|
798
795
|
* restated here rather than imported so this module stays a leaf the linter can
|
|
799
796
|
* load without a resolved build configuration. Unlike that resolver it
|
|
800
797
|
* distinguishes the two empties — an authored `""` comes back as `""` and an
|
|
801
|
-
* authored `null` as `null` — which is the whole point of the caller below
|
|
802
|
-
* (#218).
|
|
798
|
+
* authored `null` as `null` — which is the whole point of the caller below.
|
|
803
799
|
*
|
|
804
800
|
* **`blockCollides` drops the first position**, where the note's type declares
|
|
805
801
|
* a system field of that name meaning something else — the resolver's
|
|
806
|
-
* `topLevelMeans` exemption, applied from the note-level side
|
|
802
|
+
* `topLevelMeans` exemption, applied from the note-level side. See
|
|
807
803
|
* {@link collidingBlockKeys}. The caller decides per key rather than this
|
|
808
804
|
* function deciding for itself, because this module knows no type's vocabulary:
|
|
809
805
|
* the declarations arrive from the caller, as `schemas` and `vocabulary` do.
|
|
@@ -831,7 +827,7 @@ function authoredValue(fm, key, { inData = false, blockCollides = false } = {})
|
|
|
831
827
|
}
|
|
832
828
|
|
|
833
829
|
/**
|
|
834
|
-
* Two embedded items on one actor may not share `(type, shortcode)
|
|
830
|
+
* Two embedded items on one actor may not share `(type, shortcode)`.
|
|
835
831
|
*
|
|
836
832
|
* SoHL treats `(type, shortcode)` as a **logical identity**, not a lookup
|
|
837
833
|
* convenience: two documents of one type bearing one shortcode denote *the same
|
|
@@ -916,7 +912,7 @@ function checkEmbeddedShortcodes(note, blockName) {
|
|
|
916
912
|
* @param {object} [opts.index] - The link index, for the reference check. Its
|
|
917
913
|
* absence skips that check rather than reporting every reference as dead.
|
|
918
914
|
* @param {Record<string, object>} [opts.vocabulary] - Type → the closed regions
|
|
919
|
-
* it declares, as `engine/note-vocabulary.mjs` states them
|
|
915
|
+
* it declares, as `engine/note-vocabulary.mjs` states them. Supplied
|
|
920
916
|
* by the caller for the same reason `schemas` is: this module validates a
|
|
921
917
|
* note against whatever its type declares and knows no type names of its
|
|
922
918
|
* own. Its absence skips the `data:` and `subType` checks rather than
|
|
@@ -926,7 +922,7 @@ function checkEmbeddedShortcodes(note, blockName) {
|
|
|
926
922
|
* declaration, asked through `engine/generate.mjs`'s `emittedArtFor`.
|
|
927
923
|
* Supplied by the caller like `schemas`, so this module states no list of
|
|
928
924
|
* iconless types of its own; absent it, an inert `img:` goes unreported
|
|
929
|
-
* rather than every note's being
|
|
925
|
+
* rather than every note's being.
|
|
930
926
|
* @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
|
|
931
927
|
* The system blocks to check, and what each accepts. Supplied by the caller
|
|
932
928
|
* for the same reason `schemas` is — a build derives them from its
|
|
@@ -950,7 +946,7 @@ export function lintNote(
|
|
|
950
946
|
/**
|
|
951
947
|
* The in-block keys this note's own type claims for something other than
|
|
952
948
|
* the note-level field of that name, which every note-level check below
|
|
953
|
-
* reads past
|
|
949
|
+
* reads past. Resolved once: the type is fixed for the note, and
|
|
954
950
|
* each check would otherwise ask the same question of the same
|
|
955
951
|
* declarations.
|
|
956
952
|
*/
|
|
@@ -959,7 +955,7 @@ export function lintNote(
|
|
|
959
955
|
// The retired top-level fields, checked before the type: a note may carry
|
|
960
956
|
// one whatever its type is, and each finding stands on its own. Reported
|
|
961
957
|
// here as well as refused at compile because this is where an author meets
|
|
962
|
-
// every finding in the tree at once, rather than one note at a time
|
|
958
|
+
// every finding in the tree at once, rather than one note at a time.
|
|
963
959
|
if (Object.hasOwn(fm, "package")) {
|
|
964
960
|
findings.push({
|
|
965
961
|
file: note.file,
|
|
@@ -973,7 +969,7 @@ export function lintNote(
|
|
|
973
969
|
});
|
|
974
970
|
}
|
|
975
971
|
// `folder:` named a compendium folder by the raw Foundry id declared in a
|
|
976
|
-
// per-pack `*-folders.yaml`. Both halves are retired together
|
|
972
|
+
// per-pack `*-folders.yaml`. Both halves are retired together: the
|
|
977
973
|
// id spelling has nothing left to resolve against once the YAML is gone.
|
|
978
974
|
//
|
|
979
975
|
// Checked here as well as refused at compile because this is where an
|
|
@@ -1002,7 +998,7 @@ export function lintNote(
|
|
|
1002
998
|
}
|
|
1003
999
|
// `img: ""` was how a note said "I name no art" while `resolveImg`
|
|
1004
1000
|
// conflated the two empties and every caller defaulted with `||`. It now
|
|
1005
|
-
// says the opposite — "ship no art, and do not default me"
|
|
1001
|
+
// says the opposite — "ship no art, and do not default me" — so a
|
|
1006
1002
|
// note carrying the old spelling has quietly changed meaning. Forty-five
|
|
1007
1003
|
// `sohl-thalorna` notes were written under the old reading and would have
|
|
1008
1004
|
// lost their default art with no error and no warning; this is the guard
|
|
@@ -1038,14 +1034,14 @@ export function lintNote(
|
|
|
1038
1034
|
// reads; it says nothing about which position a *check* reads, and
|
|
1039
1035
|
// `authoredValue` went on resolving through the block regardless — so an
|
|
1040
1036
|
// office with no style of address answered for its note's heading, in
|
|
1041
|
-
// twenty-eight `sohl-kethira-basic` affiliations
|
|
1037
|
+
// twenty-eight `sohl-kethira-basic` affiliations. Hence
|
|
1042
1038
|
// `blockCollisions`: a note-level check reads past a block key its type
|
|
1043
1039
|
// claims for something else.
|
|
1044
1040
|
|
|
1045
1041
|
// The template priority is a *shared source* — the specification states it
|
|
1046
1042
|
// once for every type, as it does `pack` — so its retirement is reported
|
|
1047
1043
|
// here rather than by the per-type loop below, which only reaches a field
|
|
1048
|
-
// some type's vocabulary declares
|
|
1044
|
+
// some type's vocabulary declares.
|
|
1049
1045
|
if (declaresRetiredAlias(fm, "templatePriority")) {
|
|
1050
1046
|
findings.push({
|
|
1051
1047
|
file: note.file,
|
|
@@ -1065,7 +1061,7 @@ export function lintNote(
|
|
|
1065
1061
|
});
|
|
1066
1062
|
}
|
|
1067
1063
|
|
|
1068
|
-
// An art field a note's own type never emits
|
|
1064
|
+
// An art field a note's own type never emits. `img` is a *shared
|
|
1069
1065
|
// top-level* field — `BLOCK_DOCUMENT_PROPERTIES` maps it onto
|
|
1070
1066
|
// `document.img`, so it is legal on every note whatever the type — and a
|
|
1071
1067
|
// note whose document has no such property authors it, validates, compiles,
|
|
@@ -1084,7 +1080,7 @@ export function lintNote(
|
|
|
1084
1080
|
// `index` and `vocabulary` set.
|
|
1085
1081
|
//
|
|
1086
1082
|
// **Only an authored value, never `null`.** `null` is the blessed spelling
|
|
1087
|
-
// for "this note names no art"
|
|
1083
|
+
// for "this note names no art", and on a type with no art that is a
|
|
1088
1084
|
// true and harmless thing to say — it compiles identically to writing
|
|
1089
1085
|
// nothing. Twenty-six `sohl-thalorna` place notes are in exactly that
|
|
1090
1086
|
// state, and telling each of them to delete a key that already means
|
|
@@ -1140,7 +1136,7 @@ export function lintNote(
|
|
|
1140
1136
|
"meant to have no image",
|
|
1141
1137
|
});
|
|
1142
1138
|
}
|
|
1143
|
-
// `title: ""` publishes a blank heading
|
|
1139
|
+
// `title: ""` publishes a blank heading. The rule the art fields
|
|
1144
1140
|
// follow — `null` falls back, `""` is blank on purpose — reads the same way
|
|
1145
1141
|
// here, and for a *page heading* the deliberate blank is almost never what
|
|
1146
1142
|
// anyone wants: the emitter is `fm.title ?? name`, so `""` survives, the
|
|
@@ -1154,7 +1150,7 @@ export function lintNote(
|
|
|
1154
1150
|
// **The emitter reads `fm.title`, so this reads the note level.** On an
|
|
1155
1151
|
// `affiliation` `sohl.title` is the office's style of address, which the
|
|
1156
1152
|
// heading has nothing to do with — and `blockCollisions` is what keeps the
|
|
1157
|
-
// two apart
|
|
1153
|
+
// two apart. On every other type nothing claims the block key, so the
|
|
1158
1154
|
// resolution is the unchanged one.
|
|
1159
1155
|
if (authoredValue(fm, "title", { blockCollides: blockCollisions.has("title") }) === "") {
|
|
1160
1156
|
findings.push({
|
|
@@ -1179,7 +1175,7 @@ export function lintNote(
|
|
|
1179
1175
|
}
|
|
1180
1176
|
// Anchored at column 1 for the same reason `aliases` is: `section` names a
|
|
1181
1177
|
// configuration key too (`site.trees[].section`), and a nested one under
|
|
1182
|
-
// some other block is not this field
|
|
1178
|
+
// some other block is not this field.
|
|
1183
1179
|
if (Object.hasOwn(fm, "section")) {
|
|
1184
1180
|
findings.push({
|
|
1185
1181
|
file: note.file,
|
|
@@ -1191,7 +1187,7 @@ export function lintNote(
|
|
|
1191
1187
|
// Anchored at column 1 for the same reason `section` is, and with more at
|
|
1192
1188
|
// stake: `sohl.traits` is a *different field that shares the name* —
|
|
1193
1189
|
// `projectilegear` declares one and the theme's gear sidebar reads it — so
|
|
1194
|
-
// a finding about the retired top-level block must never open on it
|
|
1190
|
+
// a finding about the retired top-level block must never open on it.
|
|
1195
1191
|
if (Object.hasOwn(fm, "traits")) {
|
|
1196
1192
|
findings.push({
|
|
1197
1193
|
file: note.file,
|
|
@@ -1202,7 +1198,7 @@ export function lintNote(
|
|
|
1202
1198
|
}
|
|
1203
1199
|
// Only the top-level `aliases` is retired. `name.aliases` writes the same
|
|
1204
1200
|
// key indented under `name:` and is **permitted** — reserved and unread —
|
|
1205
|
-
// so both the test and the locator are anchored at column 1
|
|
1201
|
+
// so both the test and the locator are anchored at column 1.
|
|
1206
1202
|
if (declaresRetiredAliasesField(fm)) {
|
|
1207
1203
|
findings.push({
|
|
1208
1204
|
file: note.file,
|
|
@@ -1213,7 +1209,7 @@ export function lintNote(
|
|
|
1213
1209
|
}
|
|
1214
1210
|
|
|
1215
1211
|
// What the address rule says about a homepage's top-level fields: the
|
|
1216
|
-
// `shortcode` it owes, and the `id` it may not write
|
|
1212
|
+
// `shortcode` it owes, and the `id` it may not write. Reported
|
|
1217
1213
|
// beside the retired fields above because it is the same kind of statement
|
|
1218
1214
|
// about the same note, and, like them, it must survive the two early
|
|
1219
1215
|
// returns below: the finding stands whatever else the type is.
|
|
@@ -1224,7 +1220,7 @@ export function lintNote(
|
|
|
1224
1220
|
|
|
1225
1221
|
// A refused field must be one the note *wrote*: `resolveNoteId` fills
|
|
1226
1222
|
// `fm.id` in place, so the parsed frontmatter carries a derived id the
|
|
1227
|
-
// author never typed
|
|
1223
|
+
// author never typed. The raw text is the only place that
|
|
1228
1224
|
// distinguishes them, and `positionInFrontmatter` already answers it —
|
|
1229
1225
|
// `topLevel` so a nested `id:` under some other key is not mistaken for the
|
|
1230
1226
|
// note's own.
|
|
@@ -1242,7 +1238,7 @@ export function lintNote(
|
|
|
1242
1238
|
});
|
|
1243
1239
|
}
|
|
1244
1240
|
|
|
1245
|
-
// The type's charset, before anything that looks the type up
|
|
1241
|
+
// The type's charset, before anything that looks the type up. A
|
|
1246
1242
|
// hyphenated type is unaddressable, and every lookup below would report it
|
|
1247
1243
|
// as a type nobody declared — true, but not the reason, and it would send
|
|
1248
1244
|
// the author to declare one rather than to rename it.
|
|
@@ -1278,7 +1274,7 @@ export function lintNote(
|
|
|
1278
1274
|
// A **renamed** type is the opposite case, and the opposite answer: the
|
|
1279
1275
|
// note compiles into exactly the document it always did, so refusing it
|
|
1280
1276
|
// would fail a build over a note that is not wrong. It is reported, and
|
|
1281
|
-
// every lookup below reads the current spelling
|
|
1277
|
+
// every lookup below reads the current spelling.
|
|
1282
1278
|
const renamedTo = RENAMED_TYPES[type];
|
|
1283
1279
|
if (renamedTo) {
|
|
1284
1280
|
findings.push({
|
|
@@ -1286,7 +1282,7 @@ export function lintNote(
|
|
|
1286
1282
|
...at("type", type),
|
|
1287
1283
|
// A warning, for the reason the retired *field* alias below is one:
|
|
1288
1284
|
// the sweep is the content trees' work and the refusal comes after
|
|
1289
|
-
// it, as `package:`'s did
|
|
1285
|
+
// it, as `package:`'s did.
|
|
1290
1286
|
severity: "warning",
|
|
1291
1287
|
message: renamedTypeMessage(type, renamedTo),
|
|
1292
1288
|
});
|
|
@@ -1308,7 +1304,7 @@ export function lintNote(
|
|
|
1308
1304
|
return findings;
|
|
1309
1305
|
}
|
|
1310
1306
|
|
|
1311
|
-
// The closed frontmatter regions
|
|
1307
|
+
// The closed frontmatter regions, checked beside the `sohl:` block
|
|
1312
1308
|
// because they are the same statement about the same note: this key is not
|
|
1313
1309
|
// one this type may write. Skipped entirely when the caller declares no
|
|
1314
1310
|
// vocabulary — reporting every key as unknown because nothing was loaded
|
|
@@ -1326,7 +1322,7 @@ export function lintNote(
|
|
|
1326
1322
|
// The retired spelling of a field this type declares → what to write now.
|
|
1327
1323
|
// Built from the type's own vocabulary, so a renamed field is retired
|
|
1328
1324
|
// exactly where its replacement exists and the old name stays an unknown
|
|
1329
|
-
// key everywhere else
|
|
1325
|
+
// key everywhere else.
|
|
1330
1326
|
const renamed = new Map();
|
|
1331
1327
|
for (const name of declared) {
|
|
1332
1328
|
const retired = RETIRED_FIELD_ALIASES[name];
|
|
@@ -1344,13 +1340,13 @@ export function lintNote(
|
|
|
1344
1340
|
// A warning, not an error: the note compiles to the correct
|
|
1345
1341
|
// document, so failing a build over it would red a tree that has
|
|
1346
1342
|
// done nothing wrong yet. The refusal comes after the sweep, as
|
|
1347
|
-
// `package:`'s did
|
|
1343
|
+
// `package:`'s did.
|
|
1348
1344
|
severity: "warning",
|
|
1349
1345
|
message: retiredAliasMessage(retired, current),
|
|
1350
1346
|
});
|
|
1351
1347
|
}
|
|
1352
1348
|
|
|
1353
|
-
// Every declared system's block, each against its own vocabulary
|
|
1349
|
+
// Every declared system's block, each against its own vocabulary. A
|
|
1354
1350
|
// block carries the shared keys any system's does — `system`, `type`,
|
|
1355
1351
|
// `img`, `effects`, `flags`, `pack` — plus whatever that system declares:
|
|
1356
1352
|
// the note type's own field names for the system those schemas describe,
|
|
@@ -1358,7 +1354,7 @@ export function lintNote(
|
|
|
1358
1354
|
// here is the configuration's answer, not this module's; see
|
|
1359
1355
|
// {@link systemBlocksFor}.
|
|
1360
1356
|
for (const [blockName, spec] of Object.entries(systems ?? {})) {
|
|
1361
|
-
// Two embedded items denoting one entity
|
|
1357
|
+
// Two embedded items denoting one entity. Per block, because
|
|
1362
1358
|
// `items` is a block key and a second system's actor carries its own.
|
|
1363
1359
|
// Before the `continue` below, because it is a statement about the
|
|
1364
1360
|
// block's *shape* and holds whether or not this system declares a
|
|
@@ -1406,7 +1402,7 @@ export function lintNote(
|
|
|
1406
1402
|
// reached through its parent, and reporting the parent twice — once as
|
|
1407
1403
|
// itself and once as its child — helps nobody.
|
|
1408
1404
|
const [head] = field.name.split(".");
|
|
1409
|
-
// Resolved exactly as the compiler resolves it
|
|
1405
|
+
// Resolved exactly as the compiler resolves it: the system path
|
|
1410
1406
|
// first, then the block, then the declared shared source. A lint that
|
|
1411
1407
|
// read only one of the three would report a note's own field as missing
|
|
1412
1408
|
// the moment it moved to another of them. A **shared** field needs
|
|
@@ -1416,7 +1412,7 @@ export function lintNote(
|
|
|
1416
1412
|
// A **renamed** field may still be written under its retired spelling,
|
|
1417
1413
|
// which that order knows nothing about. It resolves through the reader
|
|
1418
1414
|
// the compiler uses, so the lint cannot disagree with the build about
|
|
1419
|
-
// which value a note carries
|
|
1415
|
+
// which value a note carries.
|
|
1420
1416
|
if ((from === "default" || value == null) && RETIRED_FIELD_ALIASES[field.name]) {
|
|
1421
1417
|
const aliased = readAliasedField(fm, field.name);
|
|
1422
1418
|
if (aliased !== undefined) {
|
|
@@ -1424,7 +1420,7 @@ export function lintNote(
|
|
|
1424
1420
|
from = "block";
|
|
1425
1421
|
}
|
|
1426
1422
|
}
|
|
1427
|
-
// The sweep's progress signal
|
|
1423
|
+
// The sweep's progress signal. A **warning**, for the reason a
|
|
1428
1424
|
// retired spelling is one: the note compiles to the correct document,
|
|
1429
1425
|
// so failing a build over it would red a tree that has done nothing
|
|
1430
1426
|
// wrong yet. The refusal comes once no tree writes the position.
|
|
@@ -1441,7 +1437,7 @@ export function lintNote(
|
|
|
1441
1437
|
// under `sohl:`, so telling an author to write `sohl.img` would send
|
|
1442
1438
|
// them to the wrong region. Nor is a field whose shared source is a
|
|
1443
1439
|
// path into `data:` — `sohl.data.species` is a region that does not
|
|
1444
|
-
// exist, and the home of that field is the container it names
|
|
1440
|
+
// exist, and the home of that field is the container it names.
|
|
1445
1441
|
const label =
|
|
1446
1442
|
field.shared || (field.name.includes(".") && field.legacyKey !== undefined) ?
|
|
1447
1443
|
`\`${field.name}\``
|
|
@@ -1476,11 +1472,10 @@ export function lintNote(
|
|
|
1476
1472
|
// would — rather than through a second, subtly different rule.
|
|
1477
1473
|
//
|
|
1478
1474
|
// **As an address, always** — which is now the only namespace there
|
|
1479
|
-
// is
|
|
1475
|
+
// is. A frontmatter reference is a bare address by construction:
|
|
1480
1476
|
// there is no pipe to read intent from, and the field supplies the
|
|
1481
1477
|
// type. The resolver once took a namespace argument, and omitting it
|
|
1482
|
-
// read every `ref:` value as an alias, which `type-shortcode` never was
|
|
1483
|
-
// (#176).
|
|
1478
|
+
// read every `ref:` value as an alias, which `type-shortcode` never was.
|
|
1484
1479
|
if (field.ref && index && typeof value === "string" && value) {
|
|
1485
1480
|
const target = `${field.ref}-${value}`;
|
|
1486
1481
|
if (!index.resolve(target) && !index.manifestHit(target)) {
|
|
@@ -1506,7 +1501,7 @@ export function lintNote(
|
|
|
1506
1501
|
* @param {object} opts
|
|
1507
1502
|
* @param {Record<string, readonly object[]>} opts.schemas - Type → declaration.
|
|
1508
1503
|
* @param {Record<string, object>} [opts.vocabulary] - Type → the closed regions
|
|
1509
|
-
* it declares
|
|
1504
|
+
* it declares; see {@link lintNote}.
|
|
1510
1505
|
* @param {boolean} [opts.references=true] - Whether to check references.
|
|
1511
1506
|
* @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
|
|
1512
1507
|
* The system blocks to check; see {@link lintNote} and
|
package/engine/frontmatter.mjs
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* list of constants, so the
|
|
20
20
|
* item-type registry (`item-builders.mjs`) can build on it without dragging in
|
|
21
21
|
* `helpers.mjs`, which reaches wikilinks — and through them back to
|
|
22
|
-
* `item-docs.mjs`, the very module that derives `itemTypes()` from the registry
|
|
23
|
-
*
|
|
22
|
+
* `item-docs.mjs`, the very module that derives `itemTypes()` from the registry.
|
|
23
|
+
* Keeping these readers dependency-free is what makes that one-way.
|
|
24
24
|
*
|
|
25
25
|
* `helpers.mjs` re-exports everything here, so existing importers are
|
|
26
26
|
* unaffected: there is still one name for each reader.
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
// The affiliation standings an authored `relation` map may use. Read from the
|
|
30
30
|
// build package rather than restated here, so the pipeline and the runtime
|
|
31
|
-
// enum cannot drift apart
|
|
31
|
+
// enum cannot drift apart — a value absent from the list is a build
|
|
32
32
|
// error, never a silent ship.
|
|
33
33
|
import { AFFILIATION_STANDINGS } from "../sohl/affiliation-standings.mjs";
|
|
34
34
|
|
|
@@ -65,7 +65,7 @@ export function sohlField(fm, key, defaultValue = undefined) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Read a `sohl:` field, seeing the **destination** position as well
|
|
68
|
+
* Read a `sohl:` field, seeing the **destination** position as well.
|
|
69
69
|
*
|
|
70
70
|
* Five declarations resolve their value by re-reading the note rather than by
|
|
71
71
|
* taking the one {@link module:engine/system-block.resolveFieldValue} handed
|
|
@@ -84,8 +84,8 @@ export function sohlField(fm, key, defaultValue = undefined) {
|
|
|
84
84
|
*
|
|
85
85
|
* `legacyKey` is for the one pair whose two positions are spelled differently:
|
|
86
86
|
* a projectile authors `impact.die` and stores `impactBase.die`, so the reader
|
|
87
|
-
* has to be told both. It is the same split `FieldSpec.name`/`legacyKey` makes
|
|
88
|
-
*
|
|
87
|
+
* has to be told both. It is the same split `FieldSpec.name`/`legacyKey` makes,
|
|
88
|
+
* for the same reason — one name cannot key two positions.
|
|
89
89
|
*
|
|
90
90
|
* @param {object} fm - The note's frontmatter.
|
|
91
91
|
* @param {string} to - The key at the destination, dotted for a nested one.
|
|
@@ -136,7 +136,7 @@ function readMapEntries(fm, key) {
|
|
|
136
136
|
/**
|
|
137
137
|
* Resolve the `charges` block shared by Mystery and Mystical Ability items.
|
|
138
138
|
*
|
|
139
|
-
* Charge usage is carried by the **maximum** alone
|
|
139
|
+
* Charge usage is carried by the **maximum** alone: a `null` max means
|
|
140
140
|
* the item does not use charges at all, `0` means it is counted but uncapped,
|
|
141
141
|
* and a positive number is a real cap. `value` is the current count, with
|
|
142
142
|
* `null` meaning "infinite remaining". Both persist as nullable NumberFields,
|
|
@@ -205,7 +205,7 @@ export function resolveSkillAptitudes(fm, ctx = "item") {
|
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* Resolve an affiliation's `relation` map — the shortcode of another
|
|
208
|
-
* affiliation → this one's standing toward it
|
|
208
|
+
* affiliation → this one's standing toward it.
|
|
209
209
|
*
|
|
210
210
|
* An unrecognized standing would fail the schema's `choices` validation at load
|
|
211
211
|
* and be dropped silently, shipping an affiliation whose authored hostility had
|
|
@@ -223,7 +223,7 @@ export function resolveSkillAptitudes(fm, ctx = "item") {
|
|
|
223
223
|
*/
|
|
224
224
|
export function resolveRelation(fm, ctx = "item") {
|
|
225
225
|
// `relations` is the field's name; `relation` is the retired spelling, read
|
|
226
|
-
// underneath it so a tree converts on its own schedule
|
|
226
|
+
// underneath it so a tree converts on its own schedule. The
|
|
227
227
|
// current name wins wherever a note writes both, and the lint reports the
|
|
228
228
|
// old one through {@link RETIRED_FIELD_ALIASES}.
|
|
229
229
|
// Probed at the destination too: a note that has moved to
|
|
@@ -307,12 +307,12 @@ export function parseValueDesc(raw) {
|
|
|
307
307
|
* (`folder-poisonsandtoxins`), resolved through the address index the whole
|
|
308
308
|
* build shares. The `folder:` Foundry-id spelling this function once read
|
|
309
309
|
* beside it, and the per-pack `*-folders.yaml` that id was resolved against,
|
|
310
|
-
* are retired together
|
|
310
|
+
* are retired together — a note declaring `folder:` is refused by
|
|
311
311
|
* {@link module:engine/folder-notes.assertNoDeclaredFolder} rather than
|
|
312
312
|
* reaching here, so there is no second source for a value to come from.
|
|
313
313
|
*
|
|
314
314
|
* **`packFolder` was a path for one release** (`Possessions/Misc_Gear/Cooking`)
|
|
315
|
-
* and is an address now
|
|
315
|
+
* and is an address now. A path encoded the hierarchy *in the value*, so
|
|
316
316
|
* reparenting a folder made every note naming it wrong — a structural edit
|
|
317
317
|
* became a corpus-wide rewrite. An address is stable under reparenting, which
|
|
318
318
|
* is exactly why a note is addressed by `(type, shortcode)` and never by
|