@heroiclands/package-build 8.1.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +748 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +479 -123
- package/bin/package-build.mjs +27 -69
- package/bin/report.mjs +1 -2
- package/bundle.mjs +2 -10
- package/config.mjs +31 -106
- package/container.mjs +13 -57
- package/content-config.mjs +300 -188
- package/coverage.mjs +14 -55
- package/deploy.mjs +4 -13
- package/docs/content-format.md +1418 -0
- package/e2e.mjs +16 -55
- package/engine/address-charset.mjs +62 -0
- package/engine/address-diff.mjs +1 -4
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +203 -31
- package/engine/code-fences.mjs +4 -13
- package/engine/compendiums.mjs +13 -37
- package/engine/content-address.mjs +6 -10
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +144 -99
- package/engine/content-lint.mjs +12 -16
- package/engine/content-slug.mjs +2 -6
- package/engine/content-tables.mjs +26 -79
- package/engine/diagnostics.mjs +37 -15
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-reference.mjs +6 -20
- package/engine/field-spec.mjs +49 -45
- package/engine/foreign-catalog.mjs +7 -22
- package/engine/foreign-manifests.mjs +1 -4
- package/engine/frontmatter-lint.mjs +347 -43
- package/engine/frontmatter.mjs +3 -8
- package/engine/generate.mjs +36 -20
- package/engine/helpers.mjs +54 -81
- package/engine/homepage.mjs +4 -15
- package/engine/ids.mjs +21 -12
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -9
- package/engine/journals.mjs +4 -14
- package/engine/kb-manifest.mjs +41 -24
- package/engine/macros.mjs +2 -10
- package/engine/manifest-emit.mjs +6 -17
- package/engine/map-notes.mjs +53 -87
- package/engine/note-claims.mjs +383 -0
- package/engine/note-package.mjs +1 -4
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +56 -60
- package/engine/pack-router.mjs +18 -8
- package/engine/prose-config.mjs +20 -4
- package/engine/prose-lint.mjs +61 -17
- package/engine/region-events.mjs +1 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scene-levels.mjs +8 -22
- package/engine/scenes.mjs +31 -47
- package/engine/schema-check.mjs +348 -7
- package/engine/schema-extract.mjs +11 -39
- package/engine/site-build.mjs +13 -38
- package/engine/site-index.mjs +40 -35
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +115 -92
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +147 -183
- package/index.mjs +1 -5
- package/lang.mjs +1 -3
- package/manifest.mjs +10 -37
- package/markdownlint-config.mjs +1 -5
- package/package.json +6 -2
- package/sohl/actors.mjs +251 -68
- package/sohl/being-info.mjs +3 -6
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +4 -6
- package/sohl/item-builders.mjs +1 -3
- package/sohl/item-fields.mjs +16 -34
- package/sohl/items.mjs +111 -17
- package/sohl/kb-passes.mjs +29 -39
- package/sohl/note-schemas.mjs +11 -7
- package/sohl/skill-base.mjs +7 -23
- package/stage.mjs +3 -13
- package/templates.mjs +4 -15
- package/types/bundle.d.mts +1 -1
- package/types/container.d.mts +2 -2
- package/types/content-config.d.mts +48 -4
- package/types/coverage.d.mts +1 -1
- package/types/e2e.d.mts +4 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +15 -2
- package/types/engine/helpers.d.mts +22 -14
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/schema-extract.d.mts +1 -1
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/manifest.d.mts +1 -1
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
package/sohl/item-fields.mjs
CHANGED
|
@@ -106,8 +106,7 @@ function noteContext(fm, type) {
|
|
|
106
106
|
/** Aptitude weights per skill selector, validated as whole numbers. */
|
|
107
107
|
const SKILL_APTITUDES = Object.freeze({
|
|
108
108
|
shape: "map of skill selector → whole number",
|
|
109
|
-
read: (_raw, { fm }) =>
|
|
110
|
-
resolveSkillAptitudes(fm, noteContext(fm, "mystery")),
|
|
109
|
+
read: (_raw, { fm }) => resolveSkillAptitudes(fm, noteContext(fm, "mystery")),
|
|
111
110
|
});
|
|
112
111
|
|
|
113
112
|
/** Standings toward other affiliations, validated against the closed list. */
|
|
@@ -127,14 +126,10 @@ const STRIKE_MODES = Object.freeze({
|
|
|
127
126
|
const seen = new Set();
|
|
128
127
|
for (const { shortcode } of raw) {
|
|
129
128
|
if (!shortcode) {
|
|
130
|
-
throw new Error(
|
|
131
|
-
"weapongear strikeModes array element requires a 'shortcode'",
|
|
132
|
-
);
|
|
129
|
+
throw new Error("weapongear strikeModes array element requires a 'shortcode'");
|
|
133
130
|
}
|
|
134
131
|
if (seen.has(shortcode)) {
|
|
135
|
-
throw new Error(
|
|
136
|
-
`weapongear has duplicate strike-mode shortcode "${shortcode}"`,
|
|
137
|
-
);
|
|
132
|
+
throw new Error(`weapongear has duplicate strike-mode shortcode "${shortcode}"`);
|
|
138
133
|
}
|
|
139
134
|
seen.add(shortcode);
|
|
140
135
|
}
|
|
@@ -218,8 +213,7 @@ const GEAR_COMMON = Object.freeze([
|
|
|
218
213
|
{
|
|
219
214
|
to: "sharedWithCohortIds",
|
|
220
215
|
value: () => [],
|
|
221
|
-
describe:
|
|
222
|
-
"Cohorts sharing the article. Possession state, never authored.",
|
|
216
|
+
describe: "Cohorts sharing the article. Possession state, never authored.",
|
|
223
217
|
},
|
|
224
218
|
{
|
|
225
219
|
to: "containerId",
|
|
@@ -250,8 +244,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
250
244
|
to: "subType",
|
|
251
245
|
...SUB_TYPE,
|
|
252
246
|
required: true,
|
|
253
|
-
describe:
|
|
254
|
-
"Which kind of affiliation this is — the society's character.",
|
|
247
|
+
describe: "Which kind of affiliation this is — the society's character.",
|
|
255
248
|
},
|
|
256
249
|
{
|
|
257
250
|
name: "society",
|
|
@@ -286,8 +279,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
286
279
|
to: "relation",
|
|
287
280
|
...RELATION,
|
|
288
281
|
default: {},
|
|
289
|
-
describe:
|
|
290
|
-
"How this society regards others: aligned, unaligned, rival or nemesis.",
|
|
282
|
+
describe: "How this society regards others: aligned, unaligned, rival or nemesis.",
|
|
291
283
|
},
|
|
292
284
|
]),
|
|
293
285
|
|
|
@@ -371,16 +363,14 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
371
363
|
to: "locations.flexible",
|
|
372
364
|
...BLANK_IS_DEFAULT,
|
|
373
365
|
default: [],
|
|
374
|
-
describe:
|
|
375
|
-
"Body locations the article covers flexibly, by location shortcode.",
|
|
366
|
+
describe: "Body locations the article covers flexibly, by location shortcode.",
|
|
376
367
|
},
|
|
377
368
|
{
|
|
378
369
|
name: "rigidloc",
|
|
379
370
|
to: "locations.rigid",
|
|
380
371
|
...BLANK_IS_DEFAULT,
|
|
381
372
|
default: [],
|
|
382
|
-
describe:
|
|
383
|
-
"Body locations the article covers rigidly, by location shortcode.",
|
|
373
|
+
describe: "Body locations the article covers rigidly, by location shortcode.",
|
|
384
374
|
},
|
|
385
375
|
{
|
|
386
376
|
name: "facing",
|
|
@@ -463,16 +453,14 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
463
453
|
to: "initDiceFormula",
|
|
464
454
|
...AS_AUTHORED,
|
|
465
455
|
default: "",
|
|
466
|
-
describe:
|
|
467
|
-
"Dice expression rolled to generate the score during character creation.",
|
|
456
|
+
describe: "Dice expression rolled to generate the score during character creation.",
|
|
468
457
|
},
|
|
469
458
|
{
|
|
470
459
|
name: "impairedByRoles",
|
|
471
460
|
to: "impairedByRoles",
|
|
472
461
|
...AS_AUTHORED,
|
|
473
462
|
default: [],
|
|
474
|
-
describe:
|
|
475
|
-
"Body-part roles whose impairment penalises tests against this attribute.",
|
|
463
|
+
describe: "Body-part roles whose impairment penalises tests against this attribute.",
|
|
476
464
|
},
|
|
477
465
|
]),
|
|
478
466
|
|
|
@@ -701,24 +689,21 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
701
689
|
ref: "skill",
|
|
702
690
|
...AS_AUTHORED,
|
|
703
691
|
default: "",
|
|
704
|
-
describe:
|
|
705
|
-
"Shortcode of the skill this one specialises, for a specialisation.",
|
|
692
|
+
describe: "Shortcode of the skill this one specialises, for a specialisation.",
|
|
706
693
|
},
|
|
707
694
|
{
|
|
708
695
|
name: "initSkillMult",
|
|
709
696
|
to: "initSkillMult",
|
|
710
697
|
...NUMBER,
|
|
711
698
|
default: 0,
|
|
712
|
-
describe:
|
|
713
|
-
"Multiplier applied to Skill Base when the skill opens on an actor.",
|
|
699
|
+
describe: "Multiplier applied to Skill Base when the skill opens on an actor.",
|
|
714
700
|
},
|
|
715
701
|
{
|
|
716
702
|
name: "impairedByRoles",
|
|
717
703
|
to: "impairedByRoles",
|
|
718
704
|
...AS_AUTHORED,
|
|
719
705
|
default: [],
|
|
720
|
-
describe:
|
|
721
|
-
"Body-part roles whose impairment penalises tests against this skill.",
|
|
706
|
+
describe: "Body-part roles whose impairment penalises tests against this skill.",
|
|
722
707
|
},
|
|
723
708
|
]),
|
|
724
709
|
|
|
@@ -742,8 +727,7 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
742
727
|
to: "levelBase",
|
|
743
728
|
...NULLABLE_COUNT,
|
|
744
729
|
default: null,
|
|
745
|
-
describe:
|
|
746
|
-
"Injury level. Unset on a descriptive condition, which has no level.",
|
|
730
|
+
describe: "Injury level. Unset on a descriptive condition, which has no level.",
|
|
747
731
|
},
|
|
748
732
|
{
|
|
749
733
|
name: "healingRateBase",
|
|
@@ -757,16 +741,14 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
757
741
|
to: "aspect",
|
|
758
742
|
...AS_AUTHORED,
|
|
759
743
|
default: null,
|
|
760
|
-
describe:
|
|
761
|
-
"How the injury was inflicted. Unset on a descriptive condition.",
|
|
744
|
+
describe: "How the injury was inflicted. Unset on a descriptive condition.",
|
|
762
745
|
},
|
|
763
746
|
{
|
|
764
747
|
name: "bodyLocationCode",
|
|
765
748
|
to: "bodyLocationCode",
|
|
766
749
|
...AS_AUTHORED,
|
|
767
750
|
default: null,
|
|
768
|
-
describe:
|
|
769
|
-
"Shortcode of the body location injured. Unset on a descriptive condition.",
|
|
751
|
+
describe: "Shortcode of the body location injured. Unset on a descriptive condition.",
|
|
770
752
|
},
|
|
771
753
|
]),
|
|
772
754
|
|
package/sohl/items.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
resolveName,
|
|
42
42
|
resolveImg,
|
|
43
43
|
defaultStats,
|
|
44
|
-
|
|
44
|
+
systemArchetype,
|
|
45
45
|
} from "../engine/helpers.mjs";
|
|
46
46
|
import { BasePackCompiler } from "../engine/base-compiler.mjs";
|
|
47
47
|
// Per-type default art lives in one framework-free module shared with the
|
|
@@ -56,6 +56,15 @@ import { itemDocEntryId, itemDocPointer } from "../engine/item-docs.mjs";
|
|
|
56
56
|
// them with are one table — the consuming repository's, not this package's
|
|
57
57
|
// (#1504/#1563).
|
|
58
58
|
import { itemTypes, itemBuilder, itemArt } from "../engine/item-registry.mjs";
|
|
59
|
+
// Which Foundry Item subtype a note's `type` compiles into. Looked up in the
|
|
60
|
+
// system's declared map, never inferred from the type itself (#79).
|
|
61
|
+
import { documentSubtype, subtypeRow } from "../engine/document-subtypes.mjs";
|
|
62
|
+
import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
63
|
+
// The note-level `sohl:` block: `sohl.system` onto the document's `system`
|
|
64
|
+
// verbatim, and `sohl.img` / `sohl.effects` / `sohl.flags` overriding their
|
|
65
|
+
// shared top-level forms for this system alone (#58).
|
|
66
|
+
import { blockProperty, claimedPaths, mergeSystemData } from "../engine/system-block.mjs";
|
|
67
|
+
import { itemFields } from "../engine/item-registry.mjs";
|
|
59
68
|
|
|
60
69
|
/**
|
|
61
70
|
* The description an item carries: a pointer to its **item doc**, the
|
|
@@ -86,11 +95,19 @@ function itemDescription(markdown, fm, name) {
|
|
|
86
95
|
|
|
87
96
|
/**
|
|
88
97
|
* Build the `system.*` fields shared by every item type:
|
|
89
|
-
* shortcode, actionDefs, notes, docHtml.
|
|
98
|
+
* shortcode, archetype, actionDefs, notes, docHtml.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} fm - The note's frontmatter.
|
|
101
|
+
* @param {string} description - The item's documentation pointer.
|
|
102
|
+
* @param {string} label - Human-readable context for error messages.
|
|
103
|
+
* @returns {object} The shared `system` fields.
|
|
90
104
|
*/
|
|
91
|
-
function commonSystem(fm, description) {
|
|
105
|
+
function commonSystem(fm, description, label) {
|
|
92
106
|
return {
|
|
93
107
|
shortcode: fm.shortcode,
|
|
108
|
+
// Required nullable number: a priority, or `null` for a document that
|
|
109
|
+
// is not an archetype (#126 / archetype contract #604).
|
|
110
|
+
archetype: systemArchetype(fm, label),
|
|
94
111
|
actionDefs: Array.isArray(fm.actionDefs) ? fm.actionDefs : [],
|
|
95
112
|
notes: "",
|
|
96
113
|
docHtml: description || "",
|
|
@@ -105,10 +122,27 @@ function commonSystem(fm, description) {
|
|
|
105
122
|
/* Compiler */
|
|
106
123
|
/* -------------------------------------------------------------------- */
|
|
107
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The system this pass compiles for — the block its notes write, and the
|
|
127
|
+
* registry its builders come from.
|
|
128
|
+
*
|
|
129
|
+
* Read from the map rather than spelled here, so the block name, the subtype
|
|
130
|
+
* map and the registry key are one statement (#58/#79).
|
|
131
|
+
*
|
|
132
|
+
* @type {string}
|
|
133
|
+
*/
|
|
134
|
+
const SYSTEM = SOHL_DOCUMENT_SUBTYPES.block;
|
|
135
|
+
|
|
108
136
|
export class Items extends BasePackCompiler {
|
|
109
137
|
static id = "items";
|
|
110
138
|
static label = "item";
|
|
111
139
|
|
|
140
|
+
/**
|
|
141
|
+
* An Item **is** a system's data, so this pack takes only notes carrying
|
|
142
|
+
* this system's block (#58).
|
|
143
|
+
*/
|
|
144
|
+
static requiresSystemBlock = true;
|
|
145
|
+
|
|
112
146
|
/**
|
|
113
147
|
* How many of each item type this pass wrote, for the summary. Every type
|
|
114
148
|
* is present from the start so the tally reads as a census of the
|
|
@@ -121,11 +155,47 @@ export class Items extends BasePackCompiler {
|
|
|
121
155
|
/**
|
|
122
156
|
* Every content type that compiles into an item.
|
|
123
157
|
*
|
|
158
|
+
* The whitelist is the consuming repository's `itemBuilders` keys (#1504),
|
|
159
|
+
* and the system's own map is a second filter on top of it: a type SoHL
|
|
160
|
+
* maps onto some *other* document class is not an item however a registry
|
|
161
|
+
* spells it, which is the "no wrongly-typed document" half of #79. A type
|
|
162
|
+
* the map does not name at all is left to the registry — see
|
|
163
|
+
* {@link Items#itemSubtype}.
|
|
164
|
+
*
|
|
124
165
|
* @param {object} fm - The note's frontmatter.
|
|
125
166
|
* @returns {boolean} True for a whitelisted item type.
|
|
126
167
|
*/
|
|
127
168
|
selects(fm) {
|
|
128
|
-
|
|
169
|
+
if (!fm.type || !itemTypes().has(fm.type)) return false;
|
|
170
|
+
const row = subtypeRow(SOHL_DOCUMENT_SUBTYPES, fm.type);
|
|
171
|
+
return !row || row.document === "Item";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The Foundry Item subtype a note compiles into.
|
|
176
|
+
*
|
|
177
|
+
* **Looked up, not inferred.** For every type this system declares, the
|
|
178
|
+
* emitted subtype is the map's, so the note vocabulary and the document
|
|
179
|
+
* vocabulary are two separately-stated things rather than one string
|
|
180
|
+
* written twice (#79).
|
|
181
|
+
*
|
|
182
|
+
* **A type the map does not name belongs to the consumer**, and its
|
|
183
|
+
* registry entry is the declaration: a repository shipping an item type of
|
|
184
|
+
* its own writes it once, in the `itemBuilders` table of its
|
|
185
|
+
* `package-build.config.yaml`, and that key is what the document is a
|
|
186
|
+
* subtype of. That is an authored statement in the consumer's own
|
|
187
|
+
* configuration, not a coincidence inside this package's source — and
|
|
188
|
+
* refusing it here would silently drop every document of a type SoHL has
|
|
189
|
+
* no opinion about (#7/#1563).
|
|
190
|
+
*
|
|
191
|
+
* @param {object} fm - The note's frontmatter.
|
|
192
|
+
* @returns {string} The document's `type`.
|
|
193
|
+
*/
|
|
194
|
+
itemSubtype(fm) {
|
|
195
|
+
const declared = documentSubtype(SOHL_DOCUMENT_SUBTYPES, fm.type, fm, {
|
|
196
|
+
absPath: this.currentNote?.absPath,
|
|
197
|
+
});
|
|
198
|
+
return declared ?? fm.type;
|
|
129
199
|
}
|
|
130
200
|
|
|
131
201
|
/** An item is named by its own type in the log, not by "item". */
|
|
@@ -147,26 +217,52 @@ export class Items extends BasePackCompiler {
|
|
|
147
217
|
const name = resolveName(fm);
|
|
148
218
|
const description = itemDescription(markdown, fm, name);
|
|
149
219
|
const id = fm.id;
|
|
220
|
+
const subType = this.itemSubtype(fm);
|
|
150
221
|
const system = {
|
|
151
|
-
...commonSystem(fm, description),
|
|
152
|
-
...itemBuilder(type)(fm),
|
|
222
|
+
...commonSystem(fm, description, `item "${name}"`),
|
|
223
|
+
...itemBuilder(type, SYSTEM)(fm),
|
|
153
224
|
};
|
|
225
|
+
// Whatever the note authors under `sohl.system`, at the DataModel's own
|
|
226
|
+
// paths. A path a declared field already writes is left to that field:
|
|
227
|
+
// its value came from the same authored place and went through the
|
|
228
|
+
// field's own coercion (#58).
|
|
229
|
+
mergeSystemData(system, fm, {
|
|
230
|
+
block: SYSTEM,
|
|
231
|
+
claimed: claimedPaths(itemFields(type, SYSTEM)),
|
|
232
|
+
});
|
|
233
|
+
this.reportUndeclaredSystemData(fm, SYSTEM, "Item", subType);
|
|
234
|
+
// And what *this* pass wrote on its own initiative — `shortcode`,
|
|
235
|
+
// `archetype`, `actionDefs`, `notes`, `docHtml` — which no field
|
|
236
|
+
// declaration states and so no other check can see (#155). Read off the
|
|
237
|
+
// assembled block, so a key added to `commonSystem` is checked without
|
|
238
|
+
// anyone remembering to list it.
|
|
239
|
+
this.reportEmittedSystemData(system, {
|
|
240
|
+
fm,
|
|
241
|
+
block: SYSTEM,
|
|
242
|
+
documentType: "Item",
|
|
243
|
+
subType,
|
|
244
|
+
type,
|
|
245
|
+
fields: itemFields(type, SYSTEM),
|
|
246
|
+
});
|
|
154
247
|
|
|
155
|
-
const effects =
|
|
156
|
-
|
|
248
|
+
const effects = blockProperty(fm, SYSTEM, "effects");
|
|
157
249
|
const folderId = sohlField(fm, "folder", null);
|
|
158
250
|
const folder = this.folderResolver(folderId);
|
|
159
251
|
|
|
160
252
|
return {
|
|
161
253
|
name,
|
|
162
|
-
type
|
|
163
|
-
|
|
254
|
+
// The note's `type` addresses the builder and the default art —
|
|
255
|
+
// both registries are keyed by content type — while the document's
|
|
256
|
+
// own subtype comes from the system's map (#79).
|
|
257
|
+
type: subType,
|
|
258
|
+
img: resolveImg(blockProperty(fm, SYSTEM, "img")) || itemArt(type, SYSTEM),
|
|
164
259
|
_id: id,
|
|
165
260
|
system,
|
|
166
|
-
effects,
|
|
167
|
-
// `
|
|
168
|
-
// `flags.sohl.docArchetype
|
|
169
|
-
|
|
261
|
+
effects: Array.isArray(effects) ? [...effects] : [],
|
|
262
|
+
// Whatever the note authors, and nothing else. `archetype` used to
|
|
263
|
+
// be spliced in here as `flags.sohl.docArchetype`; it is a schema
|
|
264
|
+
// field now and sits in `system` (#126).
|
|
265
|
+
flags: blockProperty(fm, SYSTEM, "flags", {}),
|
|
170
266
|
_stats: this.stats,
|
|
171
267
|
ownership: { default: 0 },
|
|
172
268
|
folder,
|
|
@@ -189,8 +285,6 @@ export class Items extends BasePackCompiler {
|
|
|
189
285
|
|
|
190
286
|
/** @inheritdoc */
|
|
191
287
|
reportDetail(stats) {
|
|
192
|
-
log.debug(
|
|
193
|
-
`Skipped ${stats.skippedOther} non-item file(s) (no recognized type)`,
|
|
194
|
-
);
|
|
288
|
+
log.debug(`Skipped ${stats.skippedOther} non-item file(s) (no recognized type)`);
|
|
195
289
|
}
|
|
196
290
|
}
|
package/sohl/kb-passes.mjs
CHANGED
|
@@ -90,8 +90,7 @@ function readSymbolMap(file, repoRoot) {
|
|
|
90
90
|
raw = fs.readFileSync(resolved, "utf8");
|
|
91
91
|
} catch (err) {
|
|
92
92
|
throw new Error(
|
|
93
|
-
`site.passOptions.symbolMap ${resolved} cannot be read: ` +
|
|
94
|
-
`${err.message}`,
|
|
93
|
+
`site.passOptions.symbolMap ${resolved} cannot be read: ` + `${err.message}`,
|
|
95
94
|
);
|
|
96
95
|
}
|
|
97
96
|
|
|
@@ -100,8 +99,7 @@ function readSymbolMap(file, repoRoot) {
|
|
|
100
99
|
parsed = JSON.parse(raw);
|
|
101
100
|
} catch (err) {
|
|
102
101
|
throw new Error(
|
|
103
|
-
`site.passOptions.symbolMap ${resolved} is not valid JSON: ` +
|
|
104
|
-
`${err.message}`,
|
|
102
|
+
`site.passOptions.symbolMap ${resolved} is not valid JSON: ` + `${err.message}`,
|
|
105
103
|
);
|
|
106
104
|
}
|
|
107
105
|
|
|
@@ -140,39 +138,34 @@ function readSymbolMap(file, repoRoot) {
|
|
|
140
138
|
* @returns {string} The body with every tag resolved.
|
|
141
139
|
*/
|
|
142
140
|
export function resolveApiLinks(body, symbols, apiBase) {
|
|
143
|
-
return body.replace(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
text = "";
|
|
159
|
-
}
|
|
141
|
+
return body.replace(/\{@link(code|plain)?\s+([^}]+)\}/g, (_m, kind, inner) => {
|
|
142
|
+
inner = inner.trim();
|
|
143
|
+
let target, text;
|
|
144
|
+
const pipe = inner.indexOf("|");
|
|
145
|
+
const space = inner.search(/\s/);
|
|
146
|
+
if (pipe !== -1) {
|
|
147
|
+
target = inner.slice(0, pipe).trim();
|
|
148
|
+
text = inner.slice(pipe + 1).trim();
|
|
149
|
+
} else if (space !== -1) {
|
|
150
|
+
target = inner.slice(0, space);
|
|
151
|
+
text = inner.slice(space + 1).trim();
|
|
152
|
+
} else {
|
|
153
|
+
target = inner;
|
|
154
|
+
text = "";
|
|
155
|
+
}
|
|
160
156
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
if (/^https?:\/\//.test(target)) {
|
|
158
|
+
return `[${text || target}](${target})`;
|
|
159
|
+
}
|
|
164
160
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return kind === "plain" ? display : `\`${display}\``;
|
|
174
|
-
},
|
|
175
|
-
);
|
|
161
|
+
const url = symbols[target];
|
|
162
|
+
const display = text || target.split(".").pop();
|
|
163
|
+
if (url) {
|
|
164
|
+
const href = apiBase + url;
|
|
165
|
+
return kind === "code" ? `[\`${display}\`](${href})` : `[${display}](${href})`;
|
|
166
|
+
}
|
|
167
|
+
return kind === "plain" ? display : `\`${display}\``;
|
|
168
|
+
});
|
|
176
169
|
}
|
|
177
170
|
|
|
178
171
|
/**
|
|
@@ -218,10 +211,7 @@ export function rewriteRepoLinks(body, docRel, options) {
|
|
|
218
211
|
let out;
|
|
219
212
|
if (repoRel.startsWith(`${docsRel}/`) && repoRel.endsWith(".md")) {
|
|
220
213
|
const rel2 = repoRel.slice(docsRel.length + 1, -3).toLowerCase();
|
|
221
|
-
const devPath =
|
|
222
|
-
path.basename(rel2) === "readme" ?
|
|
223
|
-
path.posix.dirname(rel2)
|
|
224
|
-
: rel2;
|
|
214
|
+
const devPath = path.basename(rel2) === "readme" ? path.posix.dirname(rel2) : rel2;
|
|
225
215
|
out = `${route}${devPath === "." ? "" : `${devPath}/`}${anchor}`;
|
|
226
216
|
} else {
|
|
227
217
|
out = `${blob}${repoRel}${anchor}`;
|
package/sohl/note-schemas.mjs
CHANGED
|
@@ -122,23 +122,29 @@ const BEING_FIELDS = Object.freeze([
|
|
|
122
122
|
]);
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* A map note — `battlemap
|
|
125
|
+
* A map note — one type whose `battlemap` / `localmap` / `regionalmap`
|
|
126
|
+
* subType decides the derived canvas (#174). Compiled into a
|
|
126
127
|
* Foundry Scene.
|
|
127
128
|
*
|
|
128
129
|
* The three differ only in derived canvas defaults, which is the map compiler's
|
|
129
130
|
* business; their authored vocabulary is the same, so they share one
|
|
130
131
|
* declaration rather than three copies that could drift.
|
|
131
132
|
*
|
|
132
|
-
* `
|
|
133
|
-
*
|
|
133
|
+
* `img` is the one required field — the compiler refuses a map note without it,
|
|
134
|
+
* since a scene with no background is not a map. It was spelled `image` and
|
|
135
|
+
* read from the `sohl:` block until #142; both spellings still compile, and the
|
|
136
|
+
* retired one is reported rather than refused.
|
|
134
137
|
*
|
|
135
138
|
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
136
139
|
*/
|
|
137
140
|
const MAP_FIELDS = Object.freeze([
|
|
138
141
|
{
|
|
139
|
-
name: "
|
|
142
|
+
name: "img",
|
|
140
143
|
...STRING,
|
|
141
144
|
required: true,
|
|
145
|
+
// Art is not system-specific — a Scene is a core Foundry document, and
|
|
146
|
+
// every other note type carries its `img` at the note's top level.
|
|
147
|
+
shared: true,
|
|
142
148
|
describe: "The scene's background image.",
|
|
143
149
|
},
|
|
144
150
|
{
|
|
@@ -324,7 +330,5 @@ export const NOTE_SCHEMAS = Object.freeze({
|
|
|
324
330
|
doc: DOC_FIELDS,
|
|
325
331
|
macro: MACRO_FIELDS,
|
|
326
332
|
being: Object.freeze([...BEING_FIELDS, ...PRESENTATION_FIELDS.being]),
|
|
327
|
-
|
|
328
|
-
localmap: MAP_FIELDS,
|
|
329
|
-
regionalmap: MAP_FIELDS,
|
|
333
|
+
map: MAP_FIELDS,
|
|
330
334
|
});
|
package/sohl/skill-base.mjs
CHANGED
|
@@ -116,9 +116,7 @@ function evalNode(node, attrs) {
|
|
|
116
116
|
switch (node.type) {
|
|
117
117
|
case "Literal": {
|
|
118
118
|
if (typeof node.value !== "number") {
|
|
119
|
-
throw new Error(
|
|
120
|
-
`unsupported literal ${JSON.stringify(node.value)}`,
|
|
121
|
-
);
|
|
119
|
+
throw new Error(`unsupported literal ${JSON.stringify(node.value)}`);
|
|
122
120
|
}
|
|
123
121
|
return node.value;
|
|
124
122
|
}
|
|
@@ -126,9 +124,7 @@ function evalNode(node, attrs) {
|
|
|
126
124
|
// Only `attr.<code>` and `attr["<code>"]`. Any other object, and
|
|
127
125
|
// any computed key that is not a plain string, is out of scope.
|
|
128
126
|
if (node.object?.type !== "Identifier") {
|
|
129
|
-
throw new Error(
|
|
130
|
-
"only `attr.<code>` member reads are supported",
|
|
131
|
-
);
|
|
127
|
+
throw new Error("only `attr.<code>` member reads are supported");
|
|
132
128
|
}
|
|
133
129
|
if (node.object.name !== "attr") {
|
|
134
130
|
throw new Error(
|
|
@@ -136,13 +132,8 @@ function evalNode(node, attrs) {
|
|
|
136
132
|
);
|
|
137
133
|
}
|
|
138
134
|
if (node.computed) {
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
typeof node.property.value !== "string"
|
|
142
|
-
) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
"a computed `attr[...]` read needs a literal string shortcode",
|
|
145
|
-
);
|
|
135
|
+
if (node.property.type !== "Literal" || typeof node.property.value !== "string") {
|
|
136
|
+
throw new Error("a computed `attr[...]` read needs a literal string shortcode");
|
|
146
137
|
}
|
|
147
138
|
return readAttr(attrs, node.property.value);
|
|
148
139
|
}
|
|
@@ -150,15 +141,11 @@ function evalNode(node, attrs) {
|
|
|
150
141
|
}
|
|
151
142
|
case "CallExpression": {
|
|
152
143
|
if (node.callee?.type !== "Identifier") {
|
|
153
|
-
throw new Error(
|
|
154
|
-
"only direct calls to a named helper are supported",
|
|
155
|
-
);
|
|
144
|
+
throw new Error("only direct calls to a named helper are supported");
|
|
156
145
|
}
|
|
157
146
|
const helper = HELPERS[node.callee.name];
|
|
158
147
|
if (!helper) {
|
|
159
|
-
throw new Error(
|
|
160
|
-
`unknown helper "${node.callee.name}()" in a skill base formula`,
|
|
161
|
-
);
|
|
148
|
+
throw new Error(`unknown helper "${node.callee.name}()" in a skill base formula`);
|
|
162
149
|
}
|
|
163
150
|
return helper(...node.arguments.map((a) => evalNode(a, attrs)));
|
|
164
151
|
}
|
|
@@ -200,10 +187,7 @@ export function evaluateSkillBase(formula, attrs = {}) {
|
|
|
200
187
|
let node;
|
|
201
188
|
try {
|
|
202
189
|
const program = parse(source, { ecmaVersion: 2022 });
|
|
203
|
-
if (
|
|
204
|
-
program.body.length !== 1 ||
|
|
205
|
-
program.body[0].type !== "ExpressionStatement"
|
|
206
|
-
) {
|
|
190
|
+
if (program.body.length !== 1 || program.body[0].type !== "ExpressionStatement") {
|
|
207
191
|
return {
|
|
208
192
|
value: 0,
|
|
209
193
|
error: `skill base formula "${source}" is not a single expression`,
|
package/stage.mjs
CHANGED
|
@@ -46,12 +46,7 @@ import path from "node:path";
|
|
|
46
46
|
* beneath `site/` — but these four are common to all of them because they come
|
|
47
47
|
* from the shared toolchain rather than from any one package's layout.
|
|
48
48
|
*/
|
|
49
|
-
export const BUILD_ARTIFACT_DIRS = Object.freeze([
|
|
50
|
-
"build",
|
|
51
|
-
".vite",
|
|
52
|
-
".vitepress",
|
|
53
|
-
".rollup.cache",
|
|
54
|
-
]);
|
|
49
|
+
export const BUILD_ARTIFACT_DIRS = Object.freeze(["build", ".vite", ".vitepress", ".rollup.cache"]);
|
|
55
50
|
|
|
56
51
|
/**
|
|
57
52
|
* A source that does not exist, described for a human.
|
|
@@ -66,9 +61,7 @@ export const BUILD_ARTIFACT_DIRS = Object.freeze([
|
|
|
66
61
|
* @returns {string[]} Every source that is absent, in the order listed.
|
|
67
62
|
*/
|
|
68
63
|
export function missingSources(entries, cwd = process.cwd()) {
|
|
69
|
-
return entries
|
|
70
|
-
.map(([src]) => src)
|
|
71
|
-
.filter((src) => !fs.existsSync(path.resolve(cwd, src)));
|
|
64
|
+
return entries.map(([src]) => src).filter((src) => !fs.existsSync(path.resolve(cwd, src)));
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
/**
|
|
@@ -157,10 +150,7 @@ export function stageAssets(entries, { cwd = process.cwd(), transform } = {}) {
|
|
|
157
150
|
* `distclean` case.
|
|
158
151
|
* @returns {string[]} The directories removed, as listed.
|
|
159
152
|
*/
|
|
160
|
-
export function cleanBuildArtifacts(
|
|
161
|
-
root,
|
|
162
|
-
{ extra = [], includeNodeModules = false } = {},
|
|
163
|
-
) {
|
|
153
|
+
export function cleanBuildArtifacts(root, { extra = [], includeNodeModules = false } = {}) {
|
|
164
154
|
const dirs = [
|
|
165
155
|
...BUILD_ARTIFACT_DIRS,
|
|
166
156
|
...extra,
|
package/templates.mjs
CHANGED
|
@@ -107,10 +107,7 @@ function stripNonProse(source) {
|
|
|
107
107
|
* @returns {boolean} Whether it should have been localized.
|
|
108
108
|
*/
|
|
109
109
|
function isProse(text, allowed) {
|
|
110
|
-
return (
|
|
111
|
-
/[A-Za-z]{2}/.test(text.replace(/&[a-zA-Z]+;|&#\d+;/g, " ")) &&
|
|
112
|
-
!allowed.has(text)
|
|
113
|
-
);
|
|
110
|
+
return /[A-Za-z]{2}/.test(text.replace(/&[a-zA-Z]+;|&#\d+;/g, " ")) && !allowed.has(text);
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
/**
|
|
@@ -201,23 +198,15 @@ export function findTemplateSyntaxErrors(source) {
|
|
|
201
198
|
// answer, in the only place this error carries it.
|
|
202
199
|
const loc = err?.hash?.loc;
|
|
203
200
|
const stated = /Parse error on line (\d+)/.exec(String(err?.message));
|
|
204
|
-
const line =
|
|
205
|
-
loc?.first_line ??
|
|
206
|
-
err?.lineNumber ??
|
|
207
|
-
(stated ? Number(stated[1]) : undefined);
|
|
201
|
+
const line = loc?.first_line ?? err?.lineNumber ?? (stated ? Number(stated[1]) : undefined);
|
|
208
202
|
return [
|
|
209
203
|
{
|
|
210
204
|
...(typeof line === "number" ? { line } : {}),
|
|
211
|
-
...(
|
|
212
|
-
typeof line === "number" &&
|
|
213
|
-
typeof loc?.first_column === "number"
|
|
214
|
-
) ?
|
|
205
|
+
...(typeof line === "number" && typeof loc?.first_column === "number" ?
|
|
215
206
|
{ column: loc.first_column + 1 }
|
|
216
207
|
: {}),
|
|
217
208
|
severity: "error",
|
|
218
|
-
message: `template does not compile: ${
|
|
219
|
-
String(err).split("\n")[0]
|
|
220
|
-
}`,
|
|
209
|
+
message: `template does not compile: ${String(err).split("\n")[0]}`,
|
|
221
210
|
},
|
|
222
211
|
];
|
|
223
212
|
}
|
package/types/bundle.d.mts
CHANGED
|
@@ -45,7 +45,7 @@ export function globalDeclarations(source: string): Array<{
|
|
|
45
45
|
* declaredAs: "esmodules"|"scripts"|"both"|"neither"}} The findings, empty
|
|
46
46
|
* when the two agree, and how the entry was declared.
|
|
47
47
|
*/
|
|
48
|
-
export function checkBundleLoading({ manifest, source, entry, manifestName
|
|
48
|
+
export function checkBundleLoading({ manifest, source, entry, manifestName }: {
|
|
49
49
|
manifest: object;
|
|
50
50
|
source: string;
|
|
51
51
|
entry: string;
|
package/types/container.d.mts
CHANGED
|
@@ -82,7 +82,7 @@ export function resolveStagePort(stage: string, { env, stages }?: {
|
|
|
82
82
|
* @param {string} [opts.e2eStage] - Which stage the suite runs against.
|
|
83
83
|
* @returns {string|null} The exact build, or `null`.
|
|
84
84
|
*/
|
|
85
|
-
export function resolveFoundryVersion(stage: string, { env, stages, compatibilityMinimum, e2eStage
|
|
85
|
+
export function resolveFoundryVersion(stage: string, { env, stages, compatibilityMinimum, e2eStage }?: {
|
|
86
86
|
env?: NodeJS.ProcessEnv | undefined;
|
|
87
87
|
stages?: Record<string, ContainerStage> | undefined;
|
|
88
88
|
compatibilityMinimum?: string | null | undefined;
|
|
@@ -305,7 +305,7 @@ export function removeContainer(name: string, log?: (message: string) => void):
|
|
|
305
305
|
* @returns {number} The exit status.
|
|
306
306
|
* @throws {Error} On an unknown action, or a stage with no usable data root.
|
|
307
307
|
*/
|
|
308
|
-
export function containerAction({ action, stage, config, env, log
|
|
308
|
+
export function containerAction({ action, stage, config, env, log }: {
|
|
309
309
|
action: string;
|
|
310
310
|
stage: string;
|
|
311
311
|
config: object;
|