@heroiclands/package-build 18.0.0 → 18.1.1
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 +56 -0
- package/docs/content-format.md +12 -14
- package/engine/frontmatter-lint.mjs +12 -1
- package/engine/homepage.mjs +16 -2
- package/engine/ids.mjs +19 -10
- package/engine/note-vocabulary.mjs +3 -3
- package/hm3/default-item-art.mjs +2 -2
- package/hm3/document-subtypes.mjs +2 -2
- package/hm3/item-fields.mjs +2 -2
- package/package.json +1 -1
- package/sohl/being-info.mjs +3 -3
- package/sohl/document-subtypes.mjs +3 -3
- package/sohl/item-fields.mjs +3 -3
- package/sohl/note-schemas.mjs +2 -2
- package/types/engine/homepage.d.mts +15 -1
- package/types/engine/ids.d.mts +16 -7
- package/types/engine/note-vocabulary.d.mts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 18.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 029cf03: **A homepage note is no longer refused for an `id` it did not author** (#319).
|
|
8
|
+
|
|
9
|
+
`resolveNoteId` fills `fm.id` **in place**, so every downstream reader sees one
|
|
10
|
+
derived value — deliberate, and documented as such. The homepage refusal
|
|
11
|
+
iterated that same object, so a note that authors no `id` was reported with a
|
|
12
|
+
message telling the author to delete a field that is not in the file.
|
|
13
|
+
|
|
14
|
+
It was an **error**, so it failed `lint:addresses`, and `lint` heads the build
|
|
15
|
+
chain — which meant it failed every pull request opened against a repository
|
|
16
|
+
carrying a homepage note, whatever that pull request changed.
|
|
17
|
+
|
|
18
|
+
A refused field must now be one the note actually wrote. The caller already owns
|
|
19
|
+
the raw note text and already positions these findings with it, so it answers
|
|
20
|
+
which keys are declared at the note's own top level; `positionInFrontmatter`'s
|
|
21
|
+
`topLevel` option is the existing helper for exactly that question, so a nested
|
|
22
|
+
`id:` under some other key is not mistaken for the note's own. With no answer
|
|
23
|
+
supplied, every key in `fm` still counts — the previous behaviour, and the right
|
|
24
|
+
one for a caller holding authored frontmatter only.
|
|
25
|
+
|
|
26
|
+
Measured on `sohl`: `lint:addresses` goes from one error to **clean across all
|
|
27
|
+
1,685 notes**.
|
|
28
|
+
|
|
29
|
+
## 18.1.0
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- e9a4200: **The gear-suffixed note types are restored** — `armorgear`, `concoctiongear`
|
|
34
|
+
and `projectilegear` are the vocabulary again, reversing #78.
|
|
35
|
+
|
|
36
|
+
#78 renamed them to `armor`, `concoction` and `projectile`, on the argument that
|
|
37
|
+
the suffix named the _SoHL document subtype_ a note compiled into rather than
|
|
38
|
+
the thing the note is about. The argument had a cost the rename did not pay for.
|
|
39
|
+
|
|
40
|
+
**Nothing adopted the bare spellings.** Across all five content trees, every note
|
|
41
|
+
still authors the suffix — 331 `armorgear` and 18 `projectilegear` in `sohl`, 71
|
|
42
|
+
`concoctiongear` in `thalorna` — and **not one note anywhere** writes `armor`,
|
|
43
|
+
`concoction` or `projectile`. The rename produced **349 warnings and zero
|
|
44
|
+
adopters**, and it was those warnings that made `lint:addresses` noise on every
|
|
45
|
+
consumer that took 18.0.0.
|
|
46
|
+
|
|
47
|
+
**It also cost a property worth more than the argument.** `weapongear` and
|
|
48
|
+
`containergear` kept their suffix — SoHL and HM3 both call those documents that —
|
|
49
|
+
so the rename left three of the five gear types spelled one way and two the
|
|
50
|
+
other. With it reversed, every SoHL row of the note-type → document-subtype map
|
|
51
|
+
is the identity again, all fourteen of them, which is what lets two tests drop
|
|
52
|
+
their exception lists entirely.
|
|
53
|
+
|
|
54
|
+
`RENAMED_TYPES` is reversed rather than emptied, so the bare spellings are still
|
|
55
|
+
_read_ and _reported_ rather than refused — the same retirement window the
|
|
56
|
+
rename itself used, pointing the other way. `armorlocation`, the type #78 added,
|
|
57
|
+
is unaffected: it was new, not renamed.
|
|
58
|
+
|
|
3
59
|
## 18.0.0
|
|
4
60
|
|
|
5
61
|
### Major Changes
|
package/docs/content-format.md
CHANGED
|
@@ -24,11 +24,11 @@ note.** A type that both systems support always shows both system boxes, and a
|
|
|
24
24
|
box whose system this particular note produces no document for reads
|
|
25
25
|
**"Not available"**.
|
|
26
26
|
|
|
27
|
-
| note type
|
|
28
|
-
|
|
|
29
|
-
| `weapon`, `skill`, `being`, `
|
|
30
|
-
| `affiliation`, `affliction`, `attribute`, `
|
|
31
|
-
| `armorlocation`
|
|
27
|
+
| note type | system boxes shown |
|
|
28
|
+
| --------------------------------------------------------------------- | -------------------------------------------------- |
|
|
29
|
+
| `weapon`, `skill`, `being`, `armorgear`, `containergear`, `miscgear`… | SoHL **and** HM3 — either may read _Not available_ |
|
|
30
|
+
| `affiliation`, `affliction`, `attribute`, `concoctiongear`, `mystery` | SoHL only |
|
|
31
|
+
| `armorlocation` | HM3 only |
|
|
32
32
|
|
|
33
33
|
This is stated rather than inferred from an empty block, because an absence is a
|
|
34
34
|
poor signal: noticing that something is missing requires already knowing it
|
|
@@ -178,7 +178,7 @@ at all in any particular system.
|
|
|
178
178
|
|
|
179
179
|
Each system then declares a map from the note's `(type, subType)` onto its own
|
|
180
180
|
document type — and, for SoHL, its own `system.subType`. The map is **declared**,
|
|
181
|
-
never inferred from a coincidence of names: `skill`, `weapongear`, `
|
|
181
|
+
never inferred from a coincidence of names: `skill`, `weapongear`, `armorgear`,
|
|
182
182
|
`containergear` and `miscgear` exist in both systems with _different_ data
|
|
183
183
|
models, so name-matching there would not fail, it would succeed wrongly.
|
|
184
184
|
|
|
@@ -902,7 +902,7 @@ A fenced `dataview` block is replaced by the table its query selects:
|
|
|
902
902
|
````markdown
|
|
903
903
|
```dataview
|
|
904
904
|
TABLE WITHOUT ID name.full AS "Name", shortcode AS "Code"
|
|
905
|
-
WHERE type = "
|
|
905
|
+
WHERE type = "armorgear"
|
|
906
906
|
```
|
|
907
907
|
````
|
|
908
908
|
|
|
@@ -1423,7 +1423,7 @@ If `sohl` is present, this becomes an `affliction` item.
|
|
|
1423
1423
|
| `data.healingCheckDurationFormula` | `system.healingCheckDurationFormula` | NA |
|
|
1424
1424
|
| `data.resolutionDurationFormula` | `system.resolutionDurationFormula` | NA |
|
|
1425
1425
|
|
|
1426
|
-
### type:
|
|
1426
|
+
### type: armorgear
|
|
1427
1427
|
|
|
1428
1428
|
Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
1429
1429
|
|
|
@@ -1437,9 +1437,7 @@ Note: `data.quantity` may not be specified. Quantity is always 1.
|
|
|
1437
1437
|
|
|
1438
1438
|
If a `sohl` property is present, a SoHL item of type "armorgear" will be created.
|
|
1439
1439
|
|
|
1440
|
-
|
|
1441
|
-
note type is `armor` in both cases: the `gear` suffix named a document subtype rather
|
|
1442
|
-
than the thing the note is about.
|
|
1440
|
+
The note type is `armorgear` in both cases. The `gear` suffix was briefly renamed away on the argument that it named a SoHL document subtype rather than the thing the note is about; that rename is reversed. Nothing had adopted the bare spelling — every note in every tree still writes the suffix — and dropping it from three of the five gear types while `weapongear` and `containergear` kept theirs cost more consistency than the argument bought.
|
|
1443
1441
|
|
|
1444
1442
|
| shared source | → sohl | → hm3 |
|
|
1445
1443
|
| ----------------- | ----------------------- | --------------- |
|
|
@@ -1471,7 +1469,7 @@ if a `sohl` property is present, a SoHL item of type "attribute" will be created
|
|
|
1471
1469
|
| shared source | → sohl | → hm3 |
|
|
1472
1470
|
| ------------- | ------ | ----- |
|
|
1473
1471
|
|
|
1474
|
-
### type:
|
|
1472
|
+
### type: concoctiongear
|
|
1475
1473
|
|
|
1476
1474
|
**subType**:
|
|
1477
1475
|
|
|
@@ -1624,7 +1622,7 @@ If an `hm3` property is present, an HM3 item is created, and `hm3.type` states w
|
|
|
1624
1622
|
| `data.charges.value` | `system.charges.value` | NA |
|
|
1625
1623
|
| `data.charges.max` | `system.charges.max` | NA |
|
|
1626
1624
|
|
|
1627
|
-
### type:
|
|
1625
|
+
### type: projectilegear
|
|
1628
1626
|
|
|
1629
1627
|
**subTypes**:
|
|
1630
1628
|
|
|
@@ -1649,7 +1647,7 @@ if a `sohl` property is present, a SoHL item of type "projectilegear" will be cr
|
|
|
1649
1647
|
If an `hm3` property is present, then an HM3 item of type "missilegear" will be created.
|
|
1650
1648
|
|
|
1651
1649
|
Note that `weapon` can also produce an HM3 `missilegear`. Since `(type, shortcode)` is a
|
|
1652
|
-
flat namespace, a `
|
|
1650
|
+
flat namespace, a `projectilegear` and a `weapon` sharing a shortcode would collide on the
|
|
1653
1651
|
HM3 side while remaining distinct on the SoHL side.
|
|
1654
1652
|
|
|
1655
1653
|
| shared source | → sohl | → hm3 |
|
|
@@ -890,7 +890,18 @@ export function lintNote(
|
|
|
890
890
|
// so the finding must survive the early returns below.
|
|
891
891
|
findings.push(...checkTags(note, { type }));
|
|
892
892
|
|
|
893
|
-
|
|
893
|
+
// A refused field must be one the note *wrote*: `resolveNoteId` fills
|
|
894
|
+
// `fm.id` in place, so the parsed frontmatter carries a derived id the
|
|
895
|
+
// author never typed (#319). The raw text is the only place that
|
|
896
|
+
// distinguishes them, and `positionInFrontmatter` already answers it —
|
|
897
|
+
// `topLevel` so a nested `id:` under some other key is not mistaken for the
|
|
898
|
+
// note's own.
|
|
899
|
+
const authoredAtTopLevel = (key) =>
|
|
900
|
+
positionInFrontmatter(note.raw ?? "", key, undefined, { topLevel: true }).line !==
|
|
901
|
+
undefined;
|
|
902
|
+
for (const { locator, message } of checkHomepageAddressFields(fm, {
|
|
903
|
+
isAuthored: authoredAtTopLevel,
|
|
904
|
+
})) {
|
|
894
905
|
findings.push({
|
|
895
906
|
file: note.file,
|
|
896
907
|
...at(locator.key, locator.literal),
|
package/engine/homepage.mjs
CHANGED
|
@@ -192,12 +192,24 @@ export const HOMEPAGE_REFUSED_FIELDS = Object.freeze(
|
|
|
192
192
|
* belong to the caller. This mirrors {@link module:engine/retired-fields},
|
|
193
193
|
* whose retired-field messages are likewise positioned by whoever reports them.
|
|
194
194
|
*
|
|
195
|
+
* **A refused field must be one the note *wrote*.** `resolveNoteId` fills
|
|
196
|
+
* `fm.id` **in place** so every downstream reader sees one derived value —
|
|
197
|
+
* deliberately, and documented as such — and this ran over the same object, so
|
|
198
|
+
* a homepage that authors no `id` was told to delete one that is not there
|
|
199
|
+
* (#319). Since the caller already owns the raw note text, it also answers
|
|
200
|
+
* which keys the note actually declared; without an answer every key in `fm`
|
|
201
|
+
* counts, which is the old behaviour and right for a caller holding authored
|
|
202
|
+
* frontmatter only.
|
|
203
|
+
*
|
|
195
204
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
205
|
+
* @param {object} [options] - Options.
|
|
206
|
+
* @param {(key: string) => boolean} [options.isAuthored] - Whether the note
|
|
207
|
+
* declares this key at its own top level. Defaults to "every key in `fm`".
|
|
196
208
|
* @returns {Array<{field: string, locator: {key: string, literal?: string},
|
|
197
209
|
* message: string}>} One entry per finding, empty for any note that is not a
|
|
198
210
|
* homepage and declares nothing wrong.
|
|
199
211
|
*/
|
|
200
|
-
export function checkHomepageAddressFields(fm) {
|
|
212
|
+
export function checkHomepageAddressFields(fm, { isAuthored } = {}) {
|
|
201
213
|
if (!isHomepage(fm)) return [];
|
|
202
214
|
const out = [];
|
|
203
215
|
|
|
@@ -218,7 +230,9 @@ export function checkHomepageAddressFields(fm) {
|
|
|
218
230
|
|
|
219
231
|
for (const key of Object.keys(fm)) {
|
|
220
232
|
const message = HOMEPAGE_REFUSED_FIELDS.get(key);
|
|
221
|
-
if (message)
|
|
233
|
+
if (!message) continue;
|
|
234
|
+
if (isAuthored && !isAuthored(key)) continue;
|
|
235
|
+
out.push({ field: key, locator: { key }, message });
|
|
222
236
|
}
|
|
223
237
|
return out;
|
|
224
238
|
}
|
package/engine/ids.mjs
CHANGED
|
@@ -186,13 +186,22 @@ export function assertTypeNotRetired(type, where) {
|
|
|
186
186
|
* reason: a consumer must be able to adopt the new toolchain before its content
|
|
187
187
|
* moves, and there are some 31,000 references to move.
|
|
188
188
|
*
|
|
189
|
-
* **Why these three
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
189
|
+
* **Why these three, and why the other way round now.** #78 renamed
|
|
190
|
+
* `armorgear`, `concoctiongear` and `projectilegear` to `armor`, `concoction`
|
|
191
|
+
* and `projectile`, on the argument that the suffix named the *SoHL document
|
|
192
|
+
* subtype* a note compiled into rather than the thing the note is about. That
|
|
193
|
+
* rename is **reversed**: the gear-suffixed spellings are the vocabulary, and
|
|
194
|
+
* the bare ones are what retires.
|
|
195
|
+
*
|
|
196
|
+
* Nothing had adopted the bare spellings, which is what makes the reversal
|
|
197
|
+
* cheap and the direction obvious: across all five content trees, **every note
|
|
198
|
+
* still authors the suffix** — 331 `armorgear` and 18 `projectilegear` in
|
|
199
|
+
* `sohl`, 71 `concoctiongear` in `thalorna`, and not one note anywhere writes
|
|
200
|
+
* `armor`, `concoction` or `projectile`. The rename produced 349 warnings and
|
|
201
|
+
* zero adopters.
|
|
202
|
+
*
|
|
203
|
+
* `weapongear` never moved, and the reversal restores the consistency its
|
|
204
|
+
* staying put had broken: all five gear types spell the suffix again.
|
|
196
205
|
*
|
|
197
206
|
* **Keyed by the retired spelling**, which is the opposite of
|
|
198
207
|
* `RETIRED_FIELD_ALIASES`. The direction follows the operation: a field alias
|
|
@@ -210,9 +219,9 @@ export function assertTypeNotRetired(type, where) {
|
|
|
210
219
|
* @type {Readonly<Record<string, string>>}
|
|
211
220
|
*/
|
|
212
221
|
export const RENAMED_TYPES = Object.freeze({
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
armor: "armorgear",
|
|
223
|
+
concoction: "concoctiongear",
|
|
224
|
+
projectile: "projectilegear",
|
|
216
225
|
});
|
|
217
226
|
|
|
218
227
|
/**
|
|
@@ -590,7 +590,7 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
590
590
|
]),
|
|
591
591
|
}),
|
|
592
592
|
|
|
593
|
-
|
|
593
|
+
armorgear: Object.freeze({
|
|
594
594
|
// Quantity is always one, so the specification refuses the key rather
|
|
595
595
|
// than defaulting it.
|
|
596
596
|
data: Object.freeze([TEMPLATE_PRIORITY, ...GEAR]),
|
|
@@ -605,7 +605,7 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
605
605
|
data: Object.freeze([TEMPLATE_PRIORITY]),
|
|
606
606
|
}),
|
|
607
607
|
|
|
608
|
-
|
|
608
|
+
concoctiongear: Object.freeze({
|
|
609
609
|
subTypes: Object.freeze(["mundane", "exotic", "elixir"]),
|
|
610
610
|
data: Object.freeze([
|
|
611
611
|
TEMPLATE_PRIORITY,
|
|
@@ -680,7 +680,7 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
680
680
|
]),
|
|
681
681
|
}),
|
|
682
682
|
|
|
683
|
-
|
|
683
|
+
projectilegear: Object.freeze({
|
|
684
684
|
subTypes: Object.freeze(["none", "arrow", "bolt", "bullet", "dart", "other"]),
|
|
685
685
|
data: Object.freeze([TEMPLATE_PRIORITY, ...GEAR, QUANTITY]),
|
|
686
686
|
}),
|
package/hm3/default-item-art.mjs
CHANGED
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
* @type {Readonly<Record<string, string>>}
|
|
43
43
|
*/
|
|
44
44
|
export const HM3_DEFAULT_ITEM_ART = Object.freeze({
|
|
45
|
-
|
|
45
|
+
armorgear: "systems/hm3/images/icons/svg/armor.svg",
|
|
46
46
|
armorlocation: "systems/hm3/images/icons/svg/anatomy.svg",
|
|
47
47
|
containergear: "systems/hm3/images/icons/svg/sack.svg",
|
|
48
48
|
miscgear: "systems/hm3/images/icons/svg/miscgear.svg",
|
|
49
49
|
mysticalability: "systems/hm3/images/icons/svg/psionics.svg",
|
|
50
|
-
|
|
50
|
+
projectilegear: "systems/hm3/images/icons/svg/arrow.svg",
|
|
51
51
|
skill: "systems/hm3/images/icons/svg/skills.svg",
|
|
52
52
|
trauma: "systems/hm3/images/icons/svg/injury.svg",
|
|
53
53
|
weapongear: "systems/hm3/images/icons/svg/sword.svg",
|
|
@@ -89,7 +89,7 @@ export const HM3_TYPE_KEY = "type";
|
|
|
89
89
|
export const HM3_DOCUMENT_SUBTYPES = defineDocumentSubtypes({
|
|
90
90
|
system: "hm3",
|
|
91
91
|
types: {
|
|
92
|
-
|
|
92
|
+
armorgear: { document: "Item", subType: "armorgear" },
|
|
93
93
|
// HM3-only: SoHL resolves a hit location from a being's own body
|
|
94
94
|
// structure, so it has no item of this kind and no note type for one.
|
|
95
95
|
armorlocation: { document: "Item", subType: "armorlocation" },
|
|
@@ -97,7 +97,7 @@ export const HM3_DOCUMENT_SUBTYPES = defineDocumentSubtypes({
|
|
|
97
97
|
miscgear: { document: "Item", subType: "miscgear" },
|
|
98
98
|
// The one outright rename. A note calls it a projectile; HM3 calls the
|
|
99
99
|
// document a missile.
|
|
100
|
-
|
|
100
|
+
projectilegear: { document: "Item", subType: "missilegear" },
|
|
101
101
|
skill: { document: "Item", subType: "skill" },
|
|
102
102
|
|
|
103
103
|
// ── the four one-to-many rows ───────────────────────────────────────
|
package/hm3/item-fields.mjs
CHANGED
|
@@ -127,7 +127,7 @@ const QUANTITY = Object.freeze({
|
|
|
127
127
|
* @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
|
|
128
128
|
*/
|
|
129
129
|
export const HM3_ITEM_FIELDS = Object.freeze({
|
|
130
|
-
|
|
130
|
+
armorgear: Object.freeze([...GEAR_COMMON]),
|
|
131
131
|
|
|
132
132
|
// The one subtype that extends the Foundry base directly, with none of the
|
|
133
133
|
// shared templates. Its content-format table has a single row, and that row
|
|
@@ -152,7 +152,7 @@ export const HM3_ITEM_FIELDS = Object.freeze({
|
|
|
152
152
|
// A note's `projectile` is HM3's `missilegear`; the fields are the gear
|
|
153
153
|
// template's, and the aspect, impact and range that make it a missile are
|
|
154
154
|
// authored under `hm3.system`.
|
|
155
|
-
|
|
155
|
+
projectilegear: Object.freeze([...GEAR_COMMON, QUANTITY]),
|
|
156
156
|
|
|
157
157
|
// `hm3.system.type` — "Craft", "Physical", "Communication", "Combat",
|
|
158
158
|
// "Magic", "Ritual" — has no shared source: the content format states
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.1.1",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
package/sohl/being-info.mjs
CHANGED
|
@@ -76,11 +76,11 @@ export function isBeing(fm) {
|
|
|
76
76
|
*/
|
|
77
77
|
export const GEAR_TYPE_TO_KEY = Object.freeze({
|
|
78
78
|
weapongear: "weapons",
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
armorgear: "armor",
|
|
80
|
+
projectilegear: "projectiles",
|
|
81
81
|
miscgear: "misc",
|
|
82
82
|
containergear: "containers",
|
|
83
|
-
|
|
83
|
+
concoctiongear: "concoctions",
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
/** Whether a value is a plain mapping. */
|
|
@@ -71,14 +71,14 @@ export const SOHL_DOCUMENT_SUBTYPES = defineDocumentSubtypes({
|
|
|
71
71
|
types: {
|
|
72
72
|
affiliation: { document: "Item", subType: "affiliation" },
|
|
73
73
|
affliction: { document: "Item", subType: "affliction" },
|
|
74
|
-
|
|
74
|
+
armorgear: { document: "Item", subType: "armorgear" },
|
|
75
75
|
attribute: { document: "Item", subType: "attribute" },
|
|
76
|
-
|
|
76
|
+
concoctiongear: { document: "Item", subType: "concoctiongear" },
|
|
77
77
|
containergear: { document: "Item", subType: "containergear" },
|
|
78
78
|
miscgear: { document: "Item", subType: "miscgear" },
|
|
79
79
|
mystery: { document: "Item", subType: "mystery" },
|
|
80
80
|
mysticalability: { document: "Item", subType: "mysticalability" },
|
|
81
|
-
|
|
81
|
+
projectilegear: { document: "Item", subType: "projectilegear" },
|
|
82
82
|
skill: { document: "Item", subType: "skill" },
|
|
83
83
|
trauma: { document: "Item", subType: "trauma" },
|
|
84
84
|
weapongear: { document: "Item", subType: "weapongear" },
|
package/sohl/item-fields.mjs
CHANGED
|
@@ -416,7 +416,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
416
416
|
},
|
|
417
417
|
]),
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
armorgear: Object.freeze([
|
|
420
420
|
...GEAR_COMMON,
|
|
421
421
|
{
|
|
422
422
|
name: "material",
|
|
@@ -531,7 +531,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
531
531
|
},
|
|
532
532
|
]),
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
concoctiongear: Object.freeze([
|
|
535
535
|
...GEAR_COMMON,
|
|
536
536
|
{
|
|
537
537
|
name: "subType",
|
|
@@ -674,7 +674,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
674
674
|
},
|
|
675
675
|
]),
|
|
676
676
|
|
|
677
|
-
|
|
677
|
+
projectilegear: Object.freeze([
|
|
678
678
|
...GEAR_COMMON,
|
|
679
679
|
{
|
|
680
680
|
name: "subType",
|
package/sohl/note-schemas.mjs
CHANGED
|
@@ -244,7 +244,7 @@ const MAP_FIELDS = Object.freeze([
|
|
|
244
244
|
* @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
|
|
245
245
|
*/
|
|
246
246
|
const PRESENTATION_FIELDS = Object.freeze({
|
|
247
|
-
|
|
247
|
+
armorgear: Object.freeze([
|
|
248
248
|
{
|
|
249
249
|
name: "craft",
|
|
250
250
|
...MAP,
|
|
@@ -291,7 +291,7 @@ const PRESENTATION_FIELDS = Object.freeze({
|
|
|
291
291
|
"The craft that makes it — `{skill, secondary}` — for knowledgebase grouping.",
|
|
292
292
|
},
|
|
293
293
|
]),
|
|
294
|
-
|
|
294
|
+
projectilegear: Object.freeze([
|
|
295
295
|
{
|
|
296
296
|
name: "craft",
|
|
297
297
|
...MAP,
|
|
@@ -42,12 +42,26 @@ export function isHomepage(fm: object | null | undefined): boolean;
|
|
|
42
42
|
* belong to the caller. This mirrors {@link module:engine/retired-fields},
|
|
43
43
|
* whose retired-field messages are likewise positioned by whoever reports them.
|
|
44
44
|
*
|
|
45
|
+
* **A refused field must be one the note *wrote*.** `resolveNoteId` fills
|
|
46
|
+
* `fm.id` **in place** so every downstream reader sees one derived value —
|
|
47
|
+
* deliberately, and documented as such — and this ran over the same object, so
|
|
48
|
+
* a homepage that authors no `id` was told to delete one that is not there
|
|
49
|
+
* (#319). Since the caller already owns the raw note text, it also answers
|
|
50
|
+
* which keys the note actually declared; without an answer every key in `fm`
|
|
51
|
+
* counts, which is the old behaviour and right for a caller holding authored
|
|
52
|
+
* frontmatter only.
|
|
53
|
+
*
|
|
45
54
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
55
|
+
* @param {object} [options] - Options.
|
|
56
|
+
* @param {(key: string) => boolean} [options.isAuthored] - Whether the note
|
|
57
|
+
* declares this key at its own top level. Defaults to "every key in `fm`".
|
|
46
58
|
* @returns {Array<{field: string, locator: {key: string, literal?: string},
|
|
47
59
|
* message: string}>} One entry per finding, empty for any note that is not a
|
|
48
60
|
* homepage and declares nothing wrong.
|
|
49
61
|
*/
|
|
50
|
-
export function checkHomepageAddressFields(fm: object | null | undefined
|
|
62
|
+
export function checkHomepageAddressFields(fm: object | null | undefined, { isAuthored }?: {
|
|
63
|
+
isAuthored?: ((key: string) => boolean) | undefined;
|
|
64
|
+
}): Array<{
|
|
51
65
|
field: string;
|
|
52
66
|
locator: {
|
|
53
67
|
key: string;
|
package/types/engine/ids.d.mts
CHANGED
|
@@ -201,13 +201,22 @@ export const RETIRED_TYPES: Readonly<Record<string, string>>;
|
|
|
201
201
|
* reason: a consumer must be able to adopt the new toolchain before its content
|
|
202
202
|
* moves, and there are some 31,000 references to move.
|
|
203
203
|
*
|
|
204
|
-
* **Why these three
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
204
|
+
* **Why these three, and why the other way round now.** #78 renamed
|
|
205
|
+
* `armorgear`, `concoctiongear` and `projectilegear` to `armor`, `concoction`
|
|
206
|
+
* and `projectile`, on the argument that the suffix named the *SoHL document
|
|
207
|
+
* subtype* a note compiled into rather than the thing the note is about. That
|
|
208
|
+
* rename is **reversed**: the gear-suffixed spellings are the vocabulary, and
|
|
209
|
+
* the bare ones are what retires.
|
|
210
|
+
*
|
|
211
|
+
* Nothing had adopted the bare spellings, which is what makes the reversal
|
|
212
|
+
* cheap and the direction obvious: across all five content trees, **every note
|
|
213
|
+
* still authors the suffix** — 331 `armorgear` and 18 `projectilegear` in
|
|
214
|
+
* `sohl`, 71 `concoctiongear` in `thalorna`, and not one note anywhere writes
|
|
215
|
+
* `armor`, `concoction` or `projectile`. The rename produced 349 warnings and
|
|
216
|
+
* zero adopters.
|
|
217
|
+
*
|
|
218
|
+
* `weapongear` never moved, and the reversal restores the consistency its
|
|
219
|
+
* staying put had broken: all five gear types spell the suffix again.
|
|
211
220
|
*
|
|
212
221
|
* **Keyed by the retired spelling**, which is the opposite of
|
|
213
222
|
* `RETIRED_FIELD_ALIASES`. The direction follows the operation: a field alias
|
|
@@ -199,7 +199,7 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
199
199
|
subTypes: readonly string[];
|
|
200
200
|
data: readonly DataFieldSpec[];
|
|
201
201
|
}>;
|
|
202
|
-
|
|
202
|
+
armorgear: Readonly<{
|
|
203
203
|
data: readonly DataFieldSpec[];
|
|
204
204
|
}>;
|
|
205
205
|
armorlocation: Readonly<{
|
|
@@ -209,7 +209,7 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
209
209
|
attribute: Readonly<{
|
|
210
210
|
data: readonly DataFieldSpec[];
|
|
211
211
|
}>;
|
|
212
|
-
|
|
212
|
+
concoctiongear: Readonly<{
|
|
213
213
|
subTypes: readonly string[];
|
|
214
214
|
data: readonly (DataFieldSpec | Readonly<{
|
|
215
215
|
describe: "How many of the thing there are; one when unstated.";
|
|
@@ -237,7 +237,7 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
237
237
|
subTypes: readonly string[];
|
|
238
238
|
data: readonly DataFieldSpec[];
|
|
239
239
|
}>;
|
|
240
|
-
|
|
240
|
+
projectilegear: Readonly<{
|
|
241
241
|
subTypes: readonly string[];
|
|
242
242
|
data: readonly (DataFieldSpec | Readonly<{
|
|
243
243
|
describe: "How many of the thing there are; one when unstated.";
|