@heroiclands/package-build 20.3.1 → 20.5.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 +351 -0
- package/CONTENT.md +322 -110
- package/README.md +21 -10
- package/bin/content-build.mjs +204 -92
- package/bin/package-build.mjs +48 -15
- package/config.mjs +6 -8
- package/content-config.mjs +518 -151
- package/docs/api.md +1352 -0
- package/docs/commands.md +1609 -0
- package/docs/configuration.md +1432 -0
- package/docs/content-format.md +73 -74
- package/docs/diagnostics.md +356 -0
- package/docs/getting-started.md +813 -0
- package/docs/project-setup.md +469 -0
- package/e2e.mjs +4 -4
- package/engine/actor-compiler.mjs +64 -61
- package/engine/address-charset.mjs +6 -6
- package/engine/address-diff.mjs +57 -52
- package/engine/anchored-sections.mjs +1 -1
- package/engine/anchors.mjs +1 -1
- package/engine/base-compiler.mjs +42 -38
- package/engine/bundle-notes.mjs +4 -4
- package/engine/bundles.mjs +17 -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 +24 -24
- 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 +196 -165
- package/engine/content-index.mjs +61 -40
- package/engine/content-links.mjs +74 -41
- package/engine/content-lint.mjs +64 -34
- package/engine/content-package.mjs +7 -9
- package/engine/content-slug.mjs +2 -2
- package/engine/content-tables.mjs +38 -34
- 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 +18 -16
- package/engine/foreign-catalog.mjs +8 -9
- package/engine/foundry-entries.mjs +19 -20
- package/engine/frontmatter-lint.mjs +95 -103
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +44 -39
- package/engine/helpers.mjs +147 -84
- package/engine/homepage.mjs +20 -22
- package/engine/ids.mjs +15 -15
- package/engine/index-records.mjs +4 -4
- package/engine/index.mjs +26 -10
- 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 +24 -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 +51 -41
- 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 -8
- package/engine/note-vocabulary.mjs +56 -54
- package/engine/pack-config.mjs +39 -25
- package/engine/pack-router.mjs +0 -0
- package/engine/pdf-build.mjs +464 -0
- package/engine/pdf-fonts.mjs +420 -0
- package/engine/pdf-render.mjs +798 -0
- package/engine/pdf-toc.mjs +525 -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 +33 -25
- package/engine/schema-check.mjs +21 -21
- package/engine/schema-extract.mjs +1 -1
- package/engine/site-build.mjs +66 -50
- 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 +41 -38
- package/engine/wikilink-syntax.mjs +16 -16
- package/engine/wikilinks.mjs +40 -42
- package/hm3/actors.mjs +30 -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 +28 -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 +66 -6
- package/sohl/actors.mjs +36 -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 +28 -6
- package/sohl/kb-passes.mjs +5 -5
- package/sohl/note-schemas.mjs +12 -12
- package/sohl/skill-base.mjs +1 -1
- package/types/content-config.d.mts +91 -39
- package/types/e2e.d.mts +3 -3
- package/types/engine/actor-compiler.d.mts +45 -40
- package/types/engine/address-charset.d.mts +6 -6
- package/types/engine/address-diff.d.mts +60 -6
- package/types/engine/base-compiler.d.mts +27 -21
- package/types/engine/bundle-notes.d.mts +3 -3
- package/types/engine/bundles.d.mts +10 -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 +20 -20
- 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 +139 -92
- package/types/engine/content-index.d.mts +59 -15
- package/types/engine/content-links.d.mts +8 -8
- package/types/engine/content-lint.d.mts +8 -6
- package/types/engine/content-package.d.mts +6 -8
- package/types/engine/content-tables.d.mts +49 -18
- 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 +10 -9
- package/types/engine/frontmatter.d.mts +7 -7
- package/types/engine/generate.d.mts +7 -7
- package/types/engine/helpers.d.mts +128 -56
- 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 +6 -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 +12 -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 +30 -22
- 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 +31 -203
- package/types/engine/pack-config.d.mts +7 -7
- package/types/engine/pack-router.d.mts +1 -1
- package/types/engine/pdf-build.d.mts +42 -0
- package/types/engine/pdf-fonts.d.mts +30 -0
- package/types/engine/pdf-render.d.mts +144 -0
- package/types/engine/pdf-toc.d.mts +114 -0
- 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/scenes.d.mts +10 -1
- package/types/engine/schema-check.d.mts +13 -13
- package/types/engine/site-build.d.mts +52 -25
- 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 +14 -14
- package/types/hm3/actors.d.mts +9 -1
- package/types/hm3/document-subtypes.d.mts +1 -1
- package/types/hm3/items.d.mts +9 -2
- package/types/hm3/template-priority.d.mts +1 -1
- package/types/manifest.d.mts +8 -8
- package/types/release.d.mts +15 -4
- package/types/sohl/actors.d.mts +12 -4
- 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 +9 -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
|
-
*
|
|
588
|
-
* the same value: a value outside
|
|
589
|
-
* declares, and only once it is a well-formed term is the
|
|
590
|
-
* reason to refuse it.
|
|
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
|
|
586
|
+
* the same value: a value outside `ADDRESS_SEGMENT_PATTERN` is refused
|
|
587
|
+
* whatever the type declares, and only once it is a well-formed term is the
|
|
588
|
+
* type's own list the 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
|
|
@@ -677,7 +674,8 @@ function checkSubType(note, { type, entry }) {
|
|
|
677
674
|
*
|
|
678
675
|
* @param {object} note - The note.
|
|
679
676
|
* @param {object} opts
|
|
680
|
-
* @param {
|
|
677
|
+
* @param {string} opts.type - The note's declared `type`, which scopes the
|
|
678
|
+
* groups checked against.
|
|
681
679
|
* @returns {object[]} Findings.
|
|
682
680
|
*/
|
|
683
681
|
function checkTags(note, { type }) {
|
|
@@ -717,13 +715,13 @@ function checkTags(note, { type }) {
|
|
|
717
715
|
* empty value belongs to the translator, not to whichever key happens to be
|
|
718
716
|
* more common. Eleven `sohl-kethira-basic` beings write `portrait: ""` and no
|
|
719
717
|
* note in any tree writes `img: ""` on a being; a check keyed on `img` alone
|
|
720
|
-
* would have called that tree clean
|
|
718
|
+
* would have called that tree clean.
|
|
721
719
|
*
|
|
722
720
|
* **Each carries where it is authored**, because the two no longer agree. The
|
|
723
721
|
* specification puts an actor's portrait under `data:` and leaves its token art
|
|
724
722
|
* at the note's top level, so `portrait` has a third position to read and `img`
|
|
725
723
|
* does not — and a check that read only the two they share would pass a
|
|
726
|
-
* `data.portrait: ""` it could not see
|
|
724
|
+
* `data.portrait: ""` it could not see.
|
|
727
725
|
*
|
|
728
726
|
* @type {readonly {key: string, inData: boolean}[]}
|
|
729
727
|
*/
|
|
@@ -732,6 +730,27 @@ const ART_FIELDS = Object.freeze([
|
|
|
732
730
|
Object.freeze({ key: "portrait", inData: true }),
|
|
733
731
|
]);
|
|
734
732
|
|
|
733
|
+
/**
|
|
734
|
+
* The keys a field declaration is authored at **inside a system block**.
|
|
735
|
+
*
|
|
736
|
+
* The first segment of each field's in-block key: `impact.die` is authored as
|
|
737
|
+
* `impact`, and a field whose shared source moved under `data:` is authored at
|
|
738
|
+
* the `legacyKey` it declares rather than at its dotted name. Keying on
|
|
739
|
+
* the name instead would report `sohl.species` as a property no `being` has,
|
|
740
|
+
* against exactly the notes the sweep has not reached yet.
|
|
741
|
+
*
|
|
742
|
+
* Written once and read twice: the note type's own declaration answers for the
|
|
743
|
+
* system whose vocabulary the caller's `schemas` are, and a second system's
|
|
744
|
+
* registry answers for its block. Two derivations of one thing would be
|
|
745
|
+
* free to disagree about which position a note authors.
|
|
746
|
+
*
|
|
747
|
+
* @param {readonly object[]|null|undefined} schema - A type's declarations.
|
|
748
|
+
* @returns {Set<string>} The in-block keys.
|
|
749
|
+
*/
|
|
750
|
+
function inBlockKeys(schema) {
|
|
751
|
+
return new Set(authoredFields(schema ?? []).map((f) => legacyKeyOf(f).split(".")[0]));
|
|
752
|
+
}
|
|
753
|
+
|
|
735
754
|
/**
|
|
736
755
|
* The in-block keys a type's own declarations claim for a *different* quantity.
|
|
737
756
|
*
|
|
@@ -749,7 +768,7 @@ const ART_FIELDS = Object.freeze([
|
|
|
749
768
|
* "Ajaw", "Warden". Twenty-eight `sohl-kethira-basic` affiliations author
|
|
750
769
|
* `sohl.title: ""` — an office with no style of address, which is ordinary —
|
|
751
770
|
* 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
|
|
771
|
+
* of them does; their pages take `name.full` exactly as intended.
|
|
753
772
|
*
|
|
754
773
|
* Keyed on the **in-block** key — `legacyKey` where a field declares one, and
|
|
755
774
|
* its first segment where that is dotted — because that is the position a note
|
|
@@ -759,27 +778,6 @@ const ART_FIELDS = Object.freeze([
|
|
|
759
778
|
* @returns {Set<string>} The in-block keys that are not the note-level field of
|
|
760
779
|
* the same name.
|
|
761
780
|
*/
|
|
762
|
-
/**
|
|
763
|
-
* The keys a field declaration is authored at **inside a system block**.
|
|
764
|
-
*
|
|
765
|
-
* The first segment of each field's in-block key: `impact.die` is authored as
|
|
766
|
-
* `impact`, and a field whose shared source moved under `data:` is authored at
|
|
767
|
-
* the `legacyKey` it declares rather than at its dotted name (#305). Keying on
|
|
768
|
-
* the name instead would report `sohl.species` as a property no `being` has,
|
|
769
|
-
* against exactly the notes the sweep has not reached yet.
|
|
770
|
-
*
|
|
771
|
-
* Written once and read twice: the note type's own declaration answers for the
|
|
772
|
-
* system whose vocabulary the caller's `schemas` are, and a second system's
|
|
773
|
-
* registry answers for its block (#58). Two derivations of one thing would be
|
|
774
|
-
* free to disagree about which position a note authors.
|
|
775
|
-
*
|
|
776
|
-
* @param {readonly object[]|null|undefined} schema - A type's declarations.
|
|
777
|
-
* @returns {Set<string>} The in-block keys.
|
|
778
|
-
*/
|
|
779
|
-
function inBlockKeys(schema) {
|
|
780
|
-
return new Set(authoredFields(schema ?? []).map((f) => legacyKeyOf(f).split(".")[0]));
|
|
781
|
-
}
|
|
782
|
-
|
|
783
781
|
function collidingBlockKeys(schema) {
|
|
784
782
|
const keys = new Set();
|
|
785
783
|
if (!Array.isArray(schema)) return keys;
|
|
@@ -798,12 +796,11 @@ function collidingBlockKeys(schema) {
|
|
|
798
796
|
* restated here rather than imported so this module stays a leaf the linter can
|
|
799
797
|
* load without a resolved build configuration. Unlike that resolver it
|
|
800
798
|
* 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).
|
|
799
|
+
* authored `null` as `null` — which is the whole point of the caller below.
|
|
803
800
|
*
|
|
804
801
|
* **`blockCollides` drops the first position**, where the note's type declares
|
|
805
802
|
* a system field of that name meaning something else — the resolver's
|
|
806
|
-
* `topLevelMeans` exemption, applied from the note-level side
|
|
803
|
+
* `topLevelMeans` exemption, applied from the note-level side. See
|
|
807
804
|
* {@link collidingBlockKeys}. The caller decides per key rather than this
|
|
808
805
|
* function deciding for itself, because this module knows no type's vocabulary:
|
|
809
806
|
* the declarations arrive from the caller, as `schemas` and `vocabulary` do.
|
|
@@ -831,7 +828,7 @@ function authoredValue(fm, key, { inData = false, blockCollides = false } = {})
|
|
|
831
828
|
}
|
|
832
829
|
|
|
833
830
|
/**
|
|
834
|
-
* Two embedded items on one actor may not share `(type, shortcode)
|
|
831
|
+
* Two embedded items on one actor may not share `(type, shortcode)`.
|
|
835
832
|
*
|
|
836
833
|
* SoHL treats `(type, shortcode)` as a **logical identity**, not a lookup
|
|
837
834
|
* convenience: two documents of one type bearing one shortcode denote *the same
|
|
@@ -913,10 +910,11 @@ function checkEmbeddedShortcodes(note, blockName) {
|
|
|
913
910
|
* @param {object} note - A note from the link index (`{fm, file, raw, type}`).
|
|
914
911
|
* @param {object} opts
|
|
915
912
|
* @param {Record<string, readonly object[]>} opts.schemas - Type → declaration.
|
|
916
|
-
* @param {object} [opts.index] - The link index, for the reference check
|
|
917
|
-
* absence skips that check rather than
|
|
913
|
+
* @param {object} [opts.index] - The link index, for the reference check, which
|
|
914
|
+
* runs through its `referenceHit`. Its absence skips that check rather than
|
|
915
|
+
* reporting every reference as dead.
|
|
918
916
|
* @param {Record<string, object>} [opts.vocabulary] - Type → the closed regions
|
|
919
|
-
* it declares, as `engine/note-vocabulary.mjs` states them
|
|
917
|
+
* it declares, as `engine/note-vocabulary.mjs` states them. Supplied
|
|
920
918
|
* by the caller for the same reason `schemas` is: this module validates a
|
|
921
919
|
* note against whatever its type declares and knows no type names of its
|
|
922
920
|
* own. Its absence skips the `data:` and `subType` checks rather than
|
|
@@ -926,7 +924,7 @@ function checkEmbeddedShortcodes(note, blockName) {
|
|
|
926
924
|
* declaration, asked through `engine/generate.mjs`'s `emittedArtFor`.
|
|
927
925
|
* Supplied by the caller like `schemas`, so this module states no list of
|
|
928
926
|
* iconless types of its own; absent it, an inert `img:` goes unreported
|
|
929
|
-
* rather than every note's being
|
|
927
|
+
* rather than every note's being.
|
|
930
928
|
* @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
|
|
931
929
|
* The system blocks to check, and what each accepts. Supplied by the caller
|
|
932
930
|
* for the same reason `schemas` is — a build derives them from its
|
|
@@ -950,7 +948,7 @@ export function lintNote(
|
|
|
950
948
|
/**
|
|
951
949
|
* The in-block keys this note's own type claims for something other than
|
|
952
950
|
* the note-level field of that name, which every note-level check below
|
|
953
|
-
* reads past
|
|
951
|
+
* reads past. Resolved once: the type is fixed for the note, and
|
|
954
952
|
* each check would otherwise ask the same question of the same
|
|
955
953
|
* declarations.
|
|
956
954
|
*/
|
|
@@ -959,7 +957,7 @@ export function lintNote(
|
|
|
959
957
|
// The retired top-level fields, checked before the type: a note may carry
|
|
960
958
|
// one whatever its type is, and each finding stands on its own. Reported
|
|
961
959
|
// 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
|
|
960
|
+
// every finding in the tree at once, rather than one note at a time.
|
|
963
961
|
if (Object.hasOwn(fm, "package")) {
|
|
964
962
|
findings.push({
|
|
965
963
|
file: note.file,
|
|
@@ -973,7 +971,7 @@ export function lintNote(
|
|
|
973
971
|
});
|
|
974
972
|
}
|
|
975
973
|
// `folder:` named a compendium folder by the raw Foundry id declared in a
|
|
976
|
-
// per-pack `*-folders.yaml`. Both halves are retired together
|
|
974
|
+
// per-pack `*-folders.yaml`. Both halves are retired together: the
|
|
977
975
|
// id spelling has nothing left to resolve against once the YAML is gone.
|
|
978
976
|
//
|
|
979
977
|
// Checked here as well as refused at compile because this is where an
|
|
@@ -1002,7 +1000,7 @@ export function lintNote(
|
|
|
1002
1000
|
}
|
|
1003
1001
|
// `img: ""` was how a note said "I name no art" while `resolveImg`
|
|
1004
1002
|
// conflated the two empties and every caller defaulted with `||`. It now
|
|
1005
|
-
// says the opposite — "ship no art, and do not default me"
|
|
1003
|
+
// says the opposite — "ship no art, and do not default me" — so a
|
|
1006
1004
|
// note carrying the old spelling has quietly changed meaning. Forty-five
|
|
1007
1005
|
// `sohl-thalorna` notes were written under the old reading and would have
|
|
1008
1006
|
// lost their default art with no error and no warning; this is the guard
|
|
@@ -1038,14 +1036,14 @@ export function lintNote(
|
|
|
1038
1036
|
// reads; it says nothing about which position a *check* reads, and
|
|
1039
1037
|
// `authoredValue` went on resolving through the block regardless — so an
|
|
1040
1038
|
// office with no style of address answered for its note's heading, in
|
|
1041
|
-
// twenty-eight `sohl-kethira-basic` affiliations
|
|
1039
|
+
// twenty-eight `sohl-kethira-basic` affiliations. Hence
|
|
1042
1040
|
// `blockCollisions`: a note-level check reads past a block key its type
|
|
1043
1041
|
// claims for something else.
|
|
1044
1042
|
|
|
1045
1043
|
// The template priority is a *shared source* — the specification states it
|
|
1046
1044
|
// once for every type, as it does `pack` — so its retirement is reported
|
|
1047
1045
|
// here rather than by the per-type loop below, which only reaches a field
|
|
1048
|
-
// some type's vocabulary declares
|
|
1046
|
+
// some type's vocabulary declares.
|
|
1049
1047
|
if (declaresRetiredAlias(fm, "templatePriority")) {
|
|
1050
1048
|
findings.push({
|
|
1051
1049
|
file: note.file,
|
|
@@ -1065,7 +1063,7 @@ export function lintNote(
|
|
|
1065
1063
|
});
|
|
1066
1064
|
}
|
|
1067
1065
|
|
|
1068
|
-
// An art field a note's own type never emits
|
|
1066
|
+
// An art field a note's own type never emits. `img` is a *shared
|
|
1069
1067
|
// top-level* field — `BLOCK_DOCUMENT_PROPERTIES` maps it onto
|
|
1070
1068
|
// `document.img`, so it is legal on every note whatever the type — and a
|
|
1071
1069
|
// note whose document has no such property authors it, validates, compiles,
|
|
@@ -1084,7 +1082,7 @@ export function lintNote(
|
|
|
1084
1082
|
// `index` and `vocabulary` set.
|
|
1085
1083
|
//
|
|
1086
1084
|
// **Only an authored value, never `null`.** `null` is the blessed spelling
|
|
1087
|
-
// for "this note names no art"
|
|
1085
|
+
// for "this note names no art", and on a type with no art that is a
|
|
1088
1086
|
// true and harmless thing to say — it compiles identically to writing
|
|
1089
1087
|
// nothing. Twenty-six `sohl-thalorna` place notes are in exactly that
|
|
1090
1088
|
// state, and telling each of them to delete a key that already means
|
|
@@ -1140,7 +1138,7 @@ export function lintNote(
|
|
|
1140
1138
|
"meant to have no image",
|
|
1141
1139
|
});
|
|
1142
1140
|
}
|
|
1143
|
-
// `title: ""` publishes a blank heading
|
|
1141
|
+
// `title: ""` publishes a blank heading. The rule the art fields
|
|
1144
1142
|
// follow — `null` falls back, `""` is blank on purpose — reads the same way
|
|
1145
1143
|
// here, and for a *page heading* the deliberate blank is almost never what
|
|
1146
1144
|
// anyone wants: the emitter is `fm.title ?? name`, so `""` survives, the
|
|
@@ -1154,7 +1152,7 @@ export function lintNote(
|
|
|
1154
1152
|
// **The emitter reads `fm.title`, so this reads the note level.** On an
|
|
1155
1153
|
// `affiliation` `sohl.title` is the office's style of address, which the
|
|
1156
1154
|
// heading has nothing to do with — and `blockCollisions` is what keeps the
|
|
1157
|
-
// two apart
|
|
1155
|
+
// two apart. On every other type nothing claims the block key, so the
|
|
1158
1156
|
// resolution is the unchanged one.
|
|
1159
1157
|
if (authoredValue(fm, "title", { blockCollides: blockCollisions.has("title") }) === "") {
|
|
1160
1158
|
findings.push({
|
|
@@ -1179,7 +1177,7 @@ export function lintNote(
|
|
|
1179
1177
|
}
|
|
1180
1178
|
// Anchored at column 1 for the same reason `aliases` is: `section` names a
|
|
1181
1179
|
// configuration key too (`site.trees[].section`), and a nested one under
|
|
1182
|
-
// some other block is not this field
|
|
1180
|
+
// some other block is not this field.
|
|
1183
1181
|
if (Object.hasOwn(fm, "section")) {
|
|
1184
1182
|
findings.push({
|
|
1185
1183
|
file: note.file,
|
|
@@ -1191,7 +1189,7 @@ export function lintNote(
|
|
|
1191
1189
|
// Anchored at column 1 for the same reason `section` is, and with more at
|
|
1192
1190
|
// stake: `sohl.traits` is a *different field that shares the name* —
|
|
1193
1191
|
// `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
|
|
1192
|
+
// a finding about the retired top-level block must never open on it.
|
|
1195
1193
|
if (Object.hasOwn(fm, "traits")) {
|
|
1196
1194
|
findings.push({
|
|
1197
1195
|
file: note.file,
|
|
@@ -1202,7 +1200,7 @@ export function lintNote(
|
|
|
1202
1200
|
}
|
|
1203
1201
|
// Only the top-level `aliases` is retired. `name.aliases` writes the same
|
|
1204
1202
|
// key indented under `name:` and is **permitted** — reserved and unread —
|
|
1205
|
-
// so both the test and the locator are anchored at column 1
|
|
1203
|
+
// so both the test and the locator are anchored at column 1.
|
|
1206
1204
|
if (declaresRetiredAliasesField(fm)) {
|
|
1207
1205
|
findings.push({
|
|
1208
1206
|
file: note.file,
|
|
@@ -1213,7 +1211,7 @@ export function lintNote(
|
|
|
1213
1211
|
}
|
|
1214
1212
|
|
|
1215
1213
|
// What the address rule says about a homepage's top-level fields: the
|
|
1216
|
-
// `shortcode` it owes, and the `id` it may not write
|
|
1214
|
+
// `shortcode` it owes, and the `id` it may not write. Reported
|
|
1217
1215
|
// beside the retired fields above because it is the same kind of statement
|
|
1218
1216
|
// about the same note, and, like them, it must survive the two early
|
|
1219
1217
|
// returns below: the finding stands whatever else the type is.
|
|
@@ -1224,7 +1222,7 @@ export function lintNote(
|
|
|
1224
1222
|
|
|
1225
1223
|
// A refused field must be one the note *wrote*: `resolveNoteId` fills
|
|
1226
1224
|
// `fm.id` in place, so the parsed frontmatter carries a derived id the
|
|
1227
|
-
// author never typed
|
|
1225
|
+
// author never typed. The raw text is the only place that
|
|
1228
1226
|
// distinguishes them, and `positionInFrontmatter` already answers it —
|
|
1229
1227
|
// `topLevel` so a nested `id:` under some other key is not mistaken for the
|
|
1230
1228
|
// note's own.
|
|
@@ -1242,7 +1240,7 @@ export function lintNote(
|
|
|
1242
1240
|
});
|
|
1243
1241
|
}
|
|
1244
1242
|
|
|
1245
|
-
// The type's charset, before anything that looks the type up
|
|
1243
|
+
// The type's charset, before anything that looks the type up. A
|
|
1246
1244
|
// hyphenated type is unaddressable, and every lookup below would report it
|
|
1247
1245
|
// as a type nobody declared — true, but not the reason, and it would send
|
|
1248
1246
|
// the author to declare one rather than to rename it.
|
|
@@ -1278,7 +1276,7 @@ export function lintNote(
|
|
|
1278
1276
|
// A **renamed** type is the opposite case, and the opposite answer: the
|
|
1279
1277
|
// note compiles into exactly the document it always did, so refusing it
|
|
1280
1278
|
// would fail a build over a note that is not wrong. It is reported, and
|
|
1281
|
-
// every lookup below reads the current spelling
|
|
1279
|
+
// every lookup below reads the current spelling.
|
|
1282
1280
|
const renamedTo = RENAMED_TYPES[type];
|
|
1283
1281
|
if (renamedTo) {
|
|
1284
1282
|
findings.push({
|
|
@@ -1286,7 +1284,7 @@ export function lintNote(
|
|
|
1286
1284
|
...at("type", type),
|
|
1287
1285
|
// A warning, for the reason the retired *field* alias below is one:
|
|
1288
1286
|
// the sweep is the content trees' work and the refusal comes after
|
|
1289
|
-
// it, as `package:`'s did
|
|
1287
|
+
// it, as `package:`'s did.
|
|
1290
1288
|
severity: "warning",
|
|
1291
1289
|
message: renamedTypeMessage(type, renamedTo),
|
|
1292
1290
|
});
|
|
@@ -1308,7 +1306,7 @@ export function lintNote(
|
|
|
1308
1306
|
return findings;
|
|
1309
1307
|
}
|
|
1310
1308
|
|
|
1311
|
-
// The closed frontmatter regions
|
|
1309
|
+
// The closed frontmatter regions, checked beside the `sohl:` block
|
|
1312
1310
|
// because they are the same statement about the same note: this key is not
|
|
1313
1311
|
// one this type may write. Skipped entirely when the caller declares no
|
|
1314
1312
|
// vocabulary — reporting every key as unknown because nothing was loaded
|
|
@@ -1326,7 +1324,7 @@ export function lintNote(
|
|
|
1326
1324
|
// The retired spelling of a field this type declares → what to write now.
|
|
1327
1325
|
// Built from the type's own vocabulary, so a renamed field is retired
|
|
1328
1326
|
// exactly where its replacement exists and the old name stays an unknown
|
|
1329
|
-
// key everywhere else
|
|
1327
|
+
// key everywhere else.
|
|
1330
1328
|
const renamed = new Map();
|
|
1331
1329
|
for (const name of declared) {
|
|
1332
1330
|
const retired = RETIRED_FIELD_ALIASES[name];
|
|
@@ -1344,13 +1342,13 @@ export function lintNote(
|
|
|
1344
1342
|
// A warning, not an error: the note compiles to the correct
|
|
1345
1343
|
// document, so failing a build over it would red a tree that has
|
|
1346
1344
|
// done nothing wrong yet. The refusal comes after the sweep, as
|
|
1347
|
-
// `package:`'s did
|
|
1345
|
+
// `package:`'s did.
|
|
1348
1346
|
severity: "warning",
|
|
1349
1347
|
message: retiredAliasMessage(retired, current),
|
|
1350
1348
|
});
|
|
1351
1349
|
}
|
|
1352
1350
|
|
|
1353
|
-
// Every declared system's block, each against its own vocabulary
|
|
1351
|
+
// Every declared system's block, each against its own vocabulary. A
|
|
1354
1352
|
// block carries the shared keys any system's does — `system`, `type`,
|
|
1355
1353
|
// `img`, `effects`, `flags`, `pack` — plus whatever that system declares:
|
|
1356
1354
|
// the note type's own field names for the system those schemas describe,
|
|
@@ -1358,7 +1356,7 @@ export function lintNote(
|
|
|
1358
1356
|
// here is the configuration's answer, not this module's; see
|
|
1359
1357
|
// {@link systemBlocksFor}.
|
|
1360
1358
|
for (const [blockName, spec] of Object.entries(systems ?? {})) {
|
|
1361
|
-
// Two embedded items denoting one entity
|
|
1359
|
+
// Two embedded items denoting one entity. Per block, because
|
|
1362
1360
|
// `items` is a block key and a second system's actor carries its own.
|
|
1363
1361
|
// Before the `continue` below, because it is a statement about the
|
|
1364
1362
|
// block's *shape* and holds whether or not this system declares a
|
|
@@ -1406,7 +1404,7 @@ export function lintNote(
|
|
|
1406
1404
|
// reached through its parent, and reporting the parent twice — once as
|
|
1407
1405
|
// itself and once as its child — helps nobody.
|
|
1408
1406
|
const [head] = field.name.split(".");
|
|
1409
|
-
// Resolved exactly as the compiler resolves it
|
|
1407
|
+
// Resolved exactly as the compiler resolves it: the system path
|
|
1410
1408
|
// first, then the block, then the declared shared source. A lint that
|
|
1411
1409
|
// read only one of the three would report a note's own field as missing
|
|
1412
1410
|
// the moment it moved to another of them. A **shared** field needs
|
|
@@ -1416,7 +1414,7 @@ export function lintNote(
|
|
|
1416
1414
|
// A **renamed** field may still be written under its retired spelling,
|
|
1417
1415
|
// which that order knows nothing about. It resolves through the reader
|
|
1418
1416
|
// the compiler uses, so the lint cannot disagree with the build about
|
|
1419
|
-
// which value a note carries
|
|
1417
|
+
// which value a note carries.
|
|
1420
1418
|
if ((from === "default" || value == null) && RETIRED_FIELD_ALIASES[field.name]) {
|
|
1421
1419
|
const aliased = readAliasedField(fm, field.name);
|
|
1422
1420
|
if (aliased !== undefined) {
|
|
@@ -1424,7 +1422,7 @@ export function lintNote(
|
|
|
1424
1422
|
from = "block";
|
|
1425
1423
|
}
|
|
1426
1424
|
}
|
|
1427
|
-
// The sweep's progress signal
|
|
1425
|
+
// The sweep's progress signal. A **warning**, for the reason a
|
|
1428
1426
|
// retired spelling is one: the note compiles to the correct document,
|
|
1429
1427
|
// so failing a build over it would red a tree that has done nothing
|
|
1430
1428
|
// wrong yet. The refusal comes once no tree writes the position.
|
|
@@ -1441,7 +1439,7 @@ export function lintNote(
|
|
|
1441
1439
|
// under `sohl:`, so telling an author to write `sohl.img` would send
|
|
1442
1440
|
// them to the wrong region. Nor is a field whose shared source is a
|
|
1443
1441
|
// 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
|
|
1442
|
+
// exist, and the home of that field is the container it names.
|
|
1445
1443
|
const label =
|
|
1446
1444
|
field.shared || (field.name.includes(".") && field.legacyKey !== undefined) ?
|
|
1447
1445
|
`\`${field.name}\``
|
|
@@ -1470,20 +1468,14 @@ export function lintNote(
|
|
|
1470
1468
|
continue;
|
|
1471
1469
|
}
|
|
1472
1470
|
|
|
1473
|
-
// A reference names another note by shortcode
|
|
1474
|
-
//
|
|
1475
|
-
//
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1478
|
-
// **As an address, always** — which is now the only namespace there
|
|
1479
|
-
// is (#180). A frontmatter reference is a bare address by construction:
|
|
1480
|
-
// there is no pipe to read intent from, and the field supplies the
|
|
1481
|
-
// 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).
|
|
1471
|
+
// A reference names another note by shortcode, and the field supplies
|
|
1472
|
+
// the type, so the resolver is handed the whole `type-shortcode` pair.
|
|
1473
|
+
// It resolves in any reachable package: the value is persisted as
|
|
1474
|
+
// written and looked up at runtime among one actor's embedded items,
|
|
1475
|
+
// which come from every package the actor draws on.
|
|
1484
1476
|
if (field.ref && index && typeof value === "string" && value) {
|
|
1485
1477
|
const target = `${field.ref}-${value}`;
|
|
1486
|
-
if (!index.
|
|
1478
|
+
if (!index.referenceHit(target)) {
|
|
1487
1479
|
findings.push({
|
|
1488
1480
|
file: note.file,
|
|
1489
1481
|
...at(head, value),
|
|
@@ -1506,7 +1498,7 @@ export function lintNote(
|
|
|
1506
1498
|
* @param {object} opts
|
|
1507
1499
|
* @param {Record<string, readonly object[]>} opts.schemas - Type → declaration.
|
|
1508
1500
|
* @param {Record<string, object>} [opts.vocabulary] - Type → the closed regions
|
|
1509
|
-
* it declares
|
|
1501
|
+
* it declares; see {@link lintNote}.
|
|
1510
1502
|
* @param {boolean} [opts.references=true] - Whether to check references.
|
|
1511
1503
|
* @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
|
|
1512
1504
|
* 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
|