@heroiclands/package-build 8.0.0 → 9.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 +173 -0
- package/bin/content-build.mjs +44 -118
- 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 +45 -164
- package/coverage.mjs +14 -55
- package/deploy.mjs +4 -13
- package/e2e.mjs +16 -55
- package/engine/address-diff.mjs +1 -4
- package/engine/base-compiler.mjs +10 -28
- package/engine/code-fences.mjs +4 -13
- package/engine/compendiums.mjs +13 -37
- package/engine/content-address.mjs +2 -6
- package/engine/content-links.mjs +13 -44
- package/engine/content-lint.mjs +4 -15
- package/engine/content-slug.mjs +2 -6
- package/engine/content-tables.mjs +26 -79
- package/engine/diagnostics.mjs +4 -15
- package/engine/field-reference.mjs +6 -20
- package/engine/field-spec.mjs +1 -3
- package/engine/foreign-catalog.mjs +7 -22
- package/engine/foreign-manifests.mjs +1 -4
- package/engine/frontmatter-lint.mjs +6 -18
- package/engine/frontmatter.mjs +3 -8
- package/engine/generate.mjs +4 -16
- package/engine/helpers.mjs +13 -52
- package/engine/homepage.mjs +4 -15
- package/engine/ids.mjs +3 -12
- package/engine/item-registry.mjs +2 -6
- package/engine/journals.mjs +4 -14
- package/engine/kb-manifest.mjs +5 -17
- package/engine/macros.mjs +2 -10
- package/engine/manifest-emit.mjs +6 -17
- package/engine/map-notes.mjs +19 -69
- package/engine/note-package.mjs +1 -4
- package/engine/pack-config.mjs +17 -38
- package/engine/pack-router.mjs +1 -2
- package/engine/prose-config.mjs +20 -4
- package/engine/prose-lint.mjs +6 -14
- package/engine/region-events.mjs +1 -3
- package/engine/scene-levels.mjs +8 -22
- package/engine/scenes.mjs +13 -47
- package/engine/schema-check.mjs +1 -4
- package/engine/schema-extract.mjs +70 -45
- package/engine/site-build.mjs +12 -37
- package/engine/site-index.mjs +3 -15
- package/engine/web-wikilinks.mjs +4 -13
- package/engine/wikilinks.mjs +115 -167
- package/index.mjs +1 -5
- package/lang.mjs +1 -3
- package/manifest.mjs +10 -37
- package/markdownlint-config.mjs +1 -5
- package/package.json +1 -1
- package/sohl/actors.mjs +10 -40
- package/sohl/being-info.mjs +3 -6
- package/sohl/index.mjs +1 -6
- package/sohl/item-builders.mjs +1 -3
- package/sohl/item-fields.mjs +16 -34
- package/sohl/items.mjs +1 -3
- package/sohl/kb-passes.mjs +29 -39
- 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/coverage.d.mts +1 -1
- package/types/e2e.d.mts +4 -4
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +1 -1
- package/types/engine/schema-extract.d.mts +1 -1
- package/types/engine/site-index.d.mts +1 -1
- package/types/manifest.d.mts +1 -1
package/engine/map-notes.mjs
CHANGED
|
@@ -55,10 +55,7 @@ import { compendiumUuid, makeId, MAP_TYPES } from "./ids.mjs";
|
|
|
55
55
|
// The curated region-event vocabulary is shared verbatim with the runtime
|
|
56
56
|
// bridge (`SohlRegionTriggerBehavior`), so an event this build accepts is
|
|
57
57
|
// exactly one the bridge forwards.
|
|
58
|
-
import {
|
|
59
|
-
CURATED_REGION_EVENTS,
|
|
60
|
-
EXCLUDED_REGION_EVENTS,
|
|
61
|
-
} from "./region-events.mjs";
|
|
58
|
+
import { CURATED_REGION_EVENTS, EXCLUDED_REGION_EVENTS } from "./region-events.mjs";
|
|
62
59
|
|
|
63
60
|
/* -------------------------------------------------------------------- */
|
|
64
61
|
/* Note types and their canvas profiles */
|
|
@@ -266,10 +263,7 @@ export function assertPixelGeometry(coords, geom) {
|
|
|
266
263
|
* @throws {Error} When the coordinates read as pixels.
|
|
267
264
|
*/
|
|
268
265
|
export function assertGridLocation(at, geom) {
|
|
269
|
-
const [gx, gy] = [
|
|
270
|
-
geom.dimensions[0] / geom.pxPerGrid,
|
|
271
|
-
geom.dimensions[1] / geom.pxPerGrid,
|
|
272
|
-
];
|
|
266
|
+
const [gx, gy] = [geom.dimensions[0] / geom.pxPerGrid, geom.dimensions[1] / geom.pxPerGrid];
|
|
273
267
|
if (at[0] > gx || at[1] > gy) {
|
|
274
268
|
throw new Error(
|
|
275
269
|
`${geom.label}: [${at.join(", ")}] lies outside the map's ` +
|
|
@@ -408,9 +402,7 @@ function buildWall(segment, spec, geom, { sceneId, id }) {
|
|
|
408
402
|
* The shape forms a map note may author. `rect` is the short spelling of
|
|
409
403
|
* `rectangle`, as the design's own examples use.
|
|
410
404
|
*/
|
|
411
|
-
const SHAPE_FORMS = Object.freeze(
|
|
412
|
-
new Set(["rect", "rectangle", "circle", "ellipse", "polygon"]),
|
|
413
|
-
);
|
|
405
|
+
const SHAPE_FORMS = Object.freeze(new Set(["rect", "rectangle", "circle", "ellipse", "polygon"]));
|
|
414
406
|
|
|
415
407
|
/**
|
|
416
408
|
* Compile one authored shape into a Foundry shape record.
|
|
@@ -448,20 +440,14 @@ export function buildShape(spec, geom) {
|
|
|
448
440
|
}
|
|
449
441
|
const raw = spec[form];
|
|
450
442
|
if (!Array.isArray(raw) || raw.some((n) => !Number.isFinite(n))) {
|
|
451
|
-
throw new Error(
|
|
452
|
-
`${geom.label}: ${form} takes a flat list of numbers in pixels`,
|
|
453
|
-
);
|
|
443
|
+
throw new Error(`${geom.label}: ${form} takes a flat list of numbers in pixels`);
|
|
454
444
|
}
|
|
455
445
|
assertPixelGeometry(raw, geom);
|
|
456
446
|
|
|
457
447
|
switch (form) {
|
|
458
448
|
case "rect":
|
|
459
449
|
case "rectangle":
|
|
460
|
-
expectLength(
|
|
461
|
-
raw,
|
|
462
|
-
4,
|
|
463
|
-
`${geom.label}: rectangle is [x, y, width, height]`,
|
|
464
|
-
);
|
|
450
|
+
expectLength(raw, 4, `${geom.label}: rectangle is [x, y, width, height]`);
|
|
465
451
|
return {
|
|
466
452
|
type: "rectangle",
|
|
467
453
|
x: raw[0],
|
|
@@ -506,8 +492,7 @@ export function buildShape(spec, geom) {
|
|
|
506
492
|
// which is a line segment and encloses nothing.
|
|
507
493
|
if (raw.length < 6) {
|
|
508
494
|
throw new Error(
|
|
509
|
-
`${geom.label}: a polygon needs at least 3 points; got ` +
|
|
510
|
-
`${raw.length / 2}`,
|
|
495
|
+
`${geom.label}: a polygon needs at least 3 points; got ` + `${raw.length / 2}`,
|
|
511
496
|
);
|
|
512
497
|
}
|
|
513
498
|
return { type: "polygon", points: [...raw], hole };
|
|
@@ -601,9 +586,7 @@ const BEHAVIOR_SPECS = Object.freeze({
|
|
|
601
586
|
*
|
|
602
587
|
* @type {ReadonlySet<string>}
|
|
603
588
|
*/
|
|
604
|
-
export const REGION_BEHAVIOR_TYPES = Object.freeze(
|
|
605
|
-
new Set(Object.keys(BEHAVIOR_SPECS)),
|
|
606
|
-
);
|
|
589
|
+
export const REGION_BEHAVIOR_TYPES = Object.freeze(new Set(Object.keys(BEHAVIOR_SPECS)));
|
|
607
590
|
|
|
608
591
|
/**
|
|
609
592
|
* Behaviour types a map note may **never** carry, and why.
|
|
@@ -657,8 +640,7 @@ function buildBehavior(key, spec, ctx) {
|
|
|
657
640
|
const [type] = types;
|
|
658
641
|
|
|
659
642
|
const banned = BANNED_REGION_BEHAVIOR_TYPES.get(type);
|
|
660
|
-
if (banned)
|
|
661
|
-
throw new Error(`${label}: "${type}" is not permitted — ${banned}`);
|
|
643
|
+
if (banned) throw new Error(`${label}: "${type}" is not permitted — ${banned}`);
|
|
662
644
|
const behaviorSpec = BEHAVIOR_SPECS[type];
|
|
663
645
|
if (!behaviorSpec) {
|
|
664
646
|
throw new Error(
|
|
@@ -680,13 +662,7 @@ function buildBehavior(key, spec, ctx) {
|
|
|
680
662
|
}
|
|
681
663
|
}
|
|
682
664
|
|
|
683
|
-
const system = compileBehaviorSystem(
|
|
684
|
-
type,
|
|
685
|
-
authored,
|
|
686
|
-
behaviorSpec,
|
|
687
|
-
label,
|
|
688
|
-
ctx,
|
|
689
|
-
);
|
|
665
|
+
const system = compileBehaviorSystem(type, authored, behaviorSpec, label, ctx);
|
|
690
666
|
const id = behaviorDocId(ctx.regionId, key, spec._id);
|
|
691
667
|
const doc = {
|
|
692
668
|
_id: id,
|
|
@@ -730,23 +706,17 @@ function compileBehaviorSystem(type, authored, behaviorSpec, label, ctx) {
|
|
|
730
706
|
const { to, ...rest } = authored;
|
|
731
707
|
return {
|
|
732
708
|
...rest,
|
|
733
|
-
destinations: toList(to).map((addr) =>
|
|
734
|
-
ctx.resolveRegionRef(addr, label),
|
|
735
|
-
),
|
|
709
|
+
destinations: toList(to).map((addr) => ctx.resolveRegionRef(addr, label)),
|
|
736
710
|
};
|
|
737
711
|
}
|
|
738
712
|
case "toggleBehavior": {
|
|
739
713
|
const { enable, disable, ...rest } = authored;
|
|
740
714
|
const out = { ...rest, events: [...authored.events] };
|
|
741
715
|
if (enable) {
|
|
742
|
-
out.enable = toList(enable).map((a) =>
|
|
743
|
-
ctx.resolveBehaviorRef(a, label),
|
|
744
|
-
);
|
|
716
|
+
out.enable = toList(enable).map((a) => ctx.resolveBehaviorRef(a, label));
|
|
745
717
|
}
|
|
746
718
|
if (disable) {
|
|
747
|
-
out.disable = toList(disable).map((a) =>
|
|
748
|
-
ctx.resolveBehaviorRef(a, label),
|
|
749
|
-
);
|
|
719
|
+
out.disable = toList(disable).map((a) => ctx.resolveBehaviorRef(a, label));
|
|
750
720
|
}
|
|
751
721
|
return out;
|
|
752
722
|
}
|
|
@@ -765,9 +735,7 @@ function compileBehaviorSystem(type, authored, behaviorSpec, label, ctx) {
|
|
|
765
735
|
}
|
|
766
736
|
case "applyActiveEffect": {
|
|
767
737
|
return {
|
|
768
|
-
effects: toList(authored.effects).map((a) =>
|
|
769
|
-
ctx.resolveEffectRef(a, label),
|
|
770
|
-
),
|
|
738
|
+
effects: toList(authored.effects).map((a) => ctx.resolveEffectRef(a, label)),
|
|
771
739
|
};
|
|
772
740
|
}
|
|
773
741
|
default:
|
|
@@ -821,13 +789,7 @@ const REGION_VISIBILITY = Object.freeze({
|
|
|
821
789
|
});
|
|
822
790
|
|
|
823
791
|
/** `CONST.EDGE_RESTRICTION_TYPES`. */
|
|
824
|
-
const RESTRICTION_TYPES = Object.freeze([
|
|
825
|
-
"light",
|
|
826
|
-
"darkness",
|
|
827
|
-
"sight",
|
|
828
|
-
"sound",
|
|
829
|
-
"move",
|
|
830
|
-
]);
|
|
792
|
+
const RESTRICTION_TYPES = Object.freeze(["light", "darkness", "sight", "sound", "move"]);
|
|
831
793
|
|
|
832
794
|
/**
|
|
833
795
|
* Compile one authored region into a Region document with its behaviours.
|
|
@@ -925,9 +887,7 @@ export function buildScene(fm, ctx) {
|
|
|
925
887
|
dimensions.length !== 2 ||
|
|
926
888
|
!dimensions.every((n) => Number.isInteger(n) && n > 0)
|
|
927
889
|
) {
|
|
928
|
-
throw new Error(
|
|
929
|
-
"`dimensions` is [width, height] in whole pixels — the map's own size",
|
|
930
|
-
);
|
|
890
|
+
throw new Error("`dimensions` is [width, height] in whole pixels — the map's own size");
|
|
931
891
|
}
|
|
932
892
|
const pxPerGrid = sohl.pxPerGrid;
|
|
933
893
|
if (!Number.isInteger(pxPerGrid) || pxPerGrid <= 0) {
|
|
@@ -981,9 +941,7 @@ export function buildScene(fm, ctx) {
|
|
|
981
941
|
|
|
982
942
|
if (ctx.journalEntryId) {
|
|
983
943
|
if (!ctx.packageId) {
|
|
984
|
-
throw new Error(
|
|
985
|
-
"a map note with a journal needs `packageId` in its compile context",
|
|
986
|
-
);
|
|
944
|
+
throw new Error("a map note with a journal needs `packageId` in its compile context");
|
|
987
945
|
}
|
|
988
946
|
// `Scene.journal` is a plain ForeignDocumentField, which
|
|
989
947
|
// `ForeignDocumentField#initialize` unconditionally nulls inside a
|
|
@@ -992,12 +950,7 @@ export function buildScene(fm, ctx) {
|
|
|
992
950
|
// pack needs the flag to find the entry at all.
|
|
993
951
|
scene.journal = ctx.journalEntryId;
|
|
994
952
|
scene.flags[ctx.packageId] = {
|
|
995
|
-
docUuid: compendiumUuid(
|
|
996
|
-
ctx.packageId,
|
|
997
|
-
"doc",
|
|
998
|
-
ctx.journalEntryId,
|
|
999
|
-
ctx.journalPack,
|
|
1000
|
-
),
|
|
953
|
+
docUuid: compendiumUuid(ctx.packageId, "doc", ctx.journalEntryId, ctx.journalPack),
|
|
1001
954
|
};
|
|
1002
955
|
}
|
|
1003
956
|
return scene;
|
|
@@ -1050,9 +1003,7 @@ export function buildWalls(sohl, geom, ctx) {
|
|
|
1050
1003
|
const label = `walls.${key}`;
|
|
1051
1004
|
const segments = toList(spec.segments);
|
|
1052
1005
|
if (!segments.length) {
|
|
1053
|
-
throw new Error(
|
|
1054
|
-
`${label}: a wall group needs at least one segment`,
|
|
1055
|
-
);
|
|
1006
|
+
throw new Error(`${label}: a wall group needs at least one segment`);
|
|
1056
1007
|
}
|
|
1057
1008
|
segments.forEach((segment, i) => {
|
|
1058
1009
|
const id = makeId("scene-wall", `${ctx.sceneId}:${key}:${i}`);
|
|
@@ -1171,8 +1122,7 @@ export function buildSounds(sohl, geom, ctx) {
|
|
|
1171
1122
|
return Object.entries(sohl.sounds ?? {}).map(([key, spec]) => {
|
|
1172
1123
|
const label = `sounds.${key}`;
|
|
1173
1124
|
const [x, y] = requirePosition(spec.position, { ...geom, label });
|
|
1174
|
-
if (!spec.path)
|
|
1175
|
-
throw new Error(`${label}: an ambient sound needs a path`);
|
|
1125
|
+
if (!spec.path) throw new Error(`${label}: an ambient sound needs a path`);
|
|
1176
1126
|
const id = spec._id || makeId("scene-sound", `${ctx.sceneId}:${key}`);
|
|
1177
1127
|
return {
|
|
1178
1128
|
_id: id,
|
package/engine/note-package.mjs
CHANGED
|
@@ -104,10 +104,7 @@ export function searchableFrontmatter(fm, configured) {
|
|
|
104
104
|
* @returns {void}
|
|
105
105
|
* @throws {Error} When the note declares the field.
|
|
106
106
|
*/
|
|
107
|
-
export function assertNoDeclaredPackage(
|
|
108
|
-
fm,
|
|
109
|
-
{ file, absPath, configured } = {},
|
|
110
|
-
) {
|
|
107
|
+
export function assertNoDeclaredPackage(fm, { file, absPath, configured } = {}) {
|
|
111
108
|
if (!fm || typeof fm !== "object" || !Object.hasOwn(fm, "package")) return;
|
|
112
109
|
|
|
113
110
|
const target = configured ?? contentPackage();
|
package/engine/pack-config.mjs
CHANGED
|
@@ -81,11 +81,7 @@ import { createRequire } from "node:module";
|
|
|
81
81
|
import YAML from "yaml";
|
|
82
82
|
|
|
83
83
|
import { defineConfig, DERIVED_SYSTEM_VERSION } from "../content-config.mjs";
|
|
84
|
-
import {
|
|
85
|
-
formatDiagnostic,
|
|
86
|
-
positionOfYamlPath,
|
|
87
|
-
yamlKeyPath,
|
|
88
|
-
} from "./diagnostics.mjs";
|
|
84
|
+
import { formatDiagnostic, positionOfYamlPath, yamlKeyPath } from "./diagnostics.mjs";
|
|
89
85
|
|
|
90
86
|
/** The stem every consuming repository declares its build under. */
|
|
91
87
|
export const CONFIG_BASENAME = "package-build.config";
|
|
@@ -130,9 +126,9 @@ export const CONFIG_FILENAMES = Object.freeze([
|
|
|
130
126
|
export function findConfigFile(from) {
|
|
131
127
|
let dir = path.resolve(from);
|
|
132
128
|
for (;;) {
|
|
133
|
-
const found = CONFIG_FILENAMES.map((name) =>
|
|
134
|
-
|
|
135
|
-
)
|
|
129
|
+
const found = CONFIG_FILENAMES.map((name) => path.join(dir, name)).filter((candidate) =>
|
|
130
|
+
fs.existsSync(candidate),
|
|
131
|
+
);
|
|
136
132
|
if (found.length > 1) {
|
|
137
133
|
throw new Error(
|
|
138
134
|
`package-build: ${dir} holds more than one configuration ` +
|
|
@@ -275,11 +271,9 @@ function shippedSystemVersion(rootDir, input) {
|
|
|
275
271
|
return pkg.version;
|
|
276
272
|
}
|
|
277
273
|
|
|
278
|
-
const systemId = /** @type {Record<string, unknown>} */ (input.stats ?? {})
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
input.relationships ?? {}
|
|
282
|
-
).systems;
|
|
274
|
+
const systemId = /** @type {Record<string, unknown>} */ (input.stats ?? {}).systemId;
|
|
275
|
+
const declaredSystems = /** @type {Record<string, unknown>} */ (input.relationships ?? {})
|
|
276
|
+
.systems;
|
|
283
277
|
|
|
284
278
|
// The `systems:` block declares without requiring (#48), so it is consulted
|
|
285
279
|
// first: a package that has adopted it needs no relationship, and one that
|
|
@@ -288,10 +282,9 @@ function shippedSystemVersion(rootDir, input) {
|
|
|
288
282
|
// otherwise a single declared system is unambiguous. With several and no
|
|
289
283
|
// gate, there is no package-wide answer — each pack carries its own, and
|
|
290
284
|
// {@link statsForPack} is what reads it.
|
|
291
|
-
const systemsBlock =
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
);
|
|
285
|
+
const systemsBlock = /** @type {Record<string, {compatibility?: {verified?: string}}>} */ (
|
|
286
|
+
input.systems ?? {}
|
|
287
|
+
);
|
|
295
288
|
const systemIds = Object.keys(systemsBlock);
|
|
296
289
|
if (systemIds.length) {
|
|
297
290
|
const chosen =
|
|
@@ -300,8 +293,7 @@ function shippedSystemVersion(rootDir, input) {
|
|
|
300
293
|
: null;
|
|
301
294
|
if (chosen) {
|
|
302
295
|
const verified = systemsBlock[chosen]?.compatibility?.verified;
|
|
303
|
-
if (typeof verified === "string" && verified.length)
|
|
304
|
-
return verified;
|
|
296
|
+
if (typeof verified === "string" && verified.length) return verified;
|
|
305
297
|
}
|
|
306
298
|
// Several declared and none required: the package-wide value is
|
|
307
299
|
// deliberately absent rather than one of them picked arbitrarily.
|
|
@@ -323,11 +315,9 @@ function shippedSystemVersion(rootDir, input) {
|
|
|
323
315
|
|
|
324
316
|
const systems =
|
|
325
317
|
/** @type {{id?: string, compatibility?: {verified?: string}}[]} */ (
|
|
326
|
-
/** @type {Record<string, unknown>} */ (input.relationships ?? {})
|
|
327
|
-
.systems
|
|
318
|
+
/** @type {Record<string, unknown>} */ (input.relationships ?? {}).systems
|
|
328
319
|
) ?? [];
|
|
329
|
-
const relationship =
|
|
330
|
-
systems.find((entry) => entry?.id === systemId) ?? systems[0];
|
|
320
|
+
const relationship = systems.find((entry) => entry?.id === systemId) ?? systems[0];
|
|
331
321
|
const verified = relationship?.compatibility?.verified;
|
|
332
322
|
|
|
333
323
|
if (typeof verified !== "string" || verified.length === 0) {
|
|
@@ -413,11 +403,7 @@ export function locateConfigError(err, configPath) {
|
|
|
413
403
|
err
|
|
414
404
|
);
|
|
415
405
|
if (!(err instanceof Error)) return err;
|
|
416
|
-
if (
|
|
417
|
-
failure.located ||
|
|
418
|
-
typeof failure.field !== "string" ||
|
|
419
|
-
!failure.field
|
|
420
|
-
) {
|
|
406
|
+
if (failure.located || typeof failure.field !== "string" || !failure.field) {
|
|
421
407
|
return err;
|
|
422
408
|
}
|
|
423
409
|
if (!configPath) return err;
|
|
@@ -562,10 +548,7 @@ function loadCodeConfig(configPath) {
|
|
|
562
548
|
// arrive here. There is no YAML to locate into, but the file is known —
|
|
563
549
|
// `locateConfigError` names it and drops the line.
|
|
564
550
|
locateConfigError(err, configPath);
|
|
565
|
-
if (
|
|
566
|
-
/** @type {{ code?: string }} */ (err)?.code ===
|
|
567
|
-
"ERR_REQUIRE_ASYNC_MODULE"
|
|
568
|
-
) {
|
|
551
|
+
if (/** @type {{ code?: string }} */ (err)?.code === "ERR_REQUIRE_ASYNC_MODULE") {
|
|
569
552
|
throw new Error(
|
|
570
553
|
`package-build: ${configPath} (or something it imports) uses ` +
|
|
571
554
|
`top-level await, which the configuration cannot: it is ` +
|
|
@@ -603,8 +586,7 @@ export function loadPackConfig() {
|
|
|
603
586
|
if (loaded) return loaded;
|
|
604
587
|
|
|
605
588
|
const explicit = process.env.PACKAGE_BUILD_CONFIG;
|
|
606
|
-
const configPath =
|
|
607
|
-
explicit ? path.resolve(explicit) : findConfigFile(import.meta.dirname);
|
|
589
|
+
const configPath = explicit ? path.resolve(explicit) : findConfigFile(import.meta.dirname);
|
|
608
590
|
|
|
609
591
|
if (!configPath || !fs.existsSync(configPath)) {
|
|
610
592
|
throw new Error(
|
|
@@ -621,10 +603,7 @@ export function loadPackConfig() {
|
|
|
621
603
|
loaded =
|
|
622
604
|
configPath.endsWith(".mjs") ?
|
|
623
605
|
loadCodeConfig(configPath)
|
|
624
|
-
: configFromData(
|
|
625
|
-
YAML.parse(fs.readFileSync(configPath, "utf8")),
|
|
626
|
-
configPath,
|
|
627
|
-
);
|
|
606
|
+
: configFromData(YAML.parse(fs.readFileSync(configPath, "utf8")), configPath);
|
|
628
607
|
loadedFrom = configPath;
|
|
629
608
|
return loaded;
|
|
630
609
|
}
|
package/engine/pack-router.mjs
CHANGED
|
@@ -117,8 +117,7 @@ export function createPackRouter(packs) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/** @param {object} fm */
|
|
120
|
-
const noteLabel = (fm) =>
|
|
121
|
-
fm?.name?.full ?? fm?.shortcode ?? fm?.id ?? "a note";
|
|
120
|
+
const noteLabel = (fm) => fm?.name?.full ?? fm?.shortcode ?? fm?.id ?? "a note";
|
|
122
121
|
|
|
123
122
|
/**
|
|
124
123
|
* The pack one pass should write this note's document to.
|
package/engine/prose-config.mjs
CHANGED
|
@@ -46,7 +46,25 @@
|
|
|
46
46
|
* @type {Readonly<object>}
|
|
47
47
|
*/
|
|
48
48
|
export const PRETTIER_BASE = Object.freeze({
|
|
49
|
-
|
|
49
|
+
// 100, not the conventional 80. The usual argument for 80 is reading
|
|
50
|
+
// measure, and it does not apply here: `proseWrap` is left at Prettier's
|
|
51
|
+
// default of `preserve`, so authored prose is never reflowed at all — the
|
|
52
|
+
// notes in these trees run to a p90 of 378 characters a line and are
|
|
53
|
+
// untouched by this number. What it actually governs is TypeScript and the
|
|
54
|
+
// YAML of a note's frontmatter, and both were measurably cramped at 80.
|
|
55
|
+
//
|
|
56
|
+
// Measured before changing it, across a third of the SoHL source: at 80,
|
|
57
|
+
// Prettier *fails* to honour the limit on 1,399 lines — long string
|
|
58
|
+
// literals, `@src/…` specifiers, generic signatures — so those lines are
|
|
59
|
+
// over-width anyway and their surroundings were broken up for nothing. At
|
|
60
|
+
// 100 that falls to 75, and the file is 4.9% shorter. Past 100 the returns
|
|
61
|
+
// flatten (120 buys another 2.6%).
|
|
62
|
+
//
|
|
63
|
+
// The same knee appears in content: an item entry written in flow style —
|
|
64
|
+
// `{ shortcode: X, type: skill, name: …, system: { … } }` — typically lands
|
|
65
|
+
// in the low 90s, so 90.8% of 318,030 entries fit on one line at 80 and
|
|
66
|
+
// 95.3% at 100, with almost nothing gained in between.
|
|
67
|
+
printWidth: 100,
|
|
50
68
|
tabWidth: 4,
|
|
51
69
|
useTabs: false,
|
|
52
70
|
semi: true,
|
|
@@ -113,9 +131,7 @@ export const PRETTIER_CONFIG = Object.freeze({
|
|
|
113
131
|
* `overrides`: passing that inline is what silently did nothing (#76).
|
|
114
132
|
*/
|
|
115
133
|
export function sharedPrettierOptionsFor(file) {
|
|
116
|
-
return /\.md$/i.test(file) ?
|
|
117
|
-
{ ...PRETTIER_BASE, ...PRETTIER_MARKDOWN }
|
|
118
|
-
: { ...PRETTIER_BASE };
|
|
134
|
+
return /\.md$/i.test(file) ? { ...PRETTIER_BASE, ...PRETTIER_MARKDOWN } : { ...PRETTIER_BASE };
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
/**
|
package/engine/prose-lint.mjs
CHANGED
|
@@ -116,18 +116,13 @@ export async function checkFormatting(root, opts = {}) {
|
|
|
116
116
|
const { paths, write = false } = opts;
|
|
117
117
|
const prettier = opts.prettier ?? (await import("prettier"));
|
|
118
118
|
const base = path.resolve(root);
|
|
119
|
-
const ignorePath = IGNORE_FILES.map((name) => path.join(base, name)).filter(
|
|
120
|
-
|
|
119
|
+
const ignorePath = IGNORE_FILES.map((name) => path.join(base, name)).filter((file) =>
|
|
120
|
+
fs.existsSync(file),
|
|
121
121
|
);
|
|
122
122
|
|
|
123
|
-
const roots =
|
|
124
|
-
paths?.length ?
|
|
125
|
-
paths.map((entry) => path.resolve(base, entry))
|
|
126
|
-
: [base];
|
|
123
|
+
const roots = paths?.length ? paths.map((entry) => path.resolve(base, entry)) : [base];
|
|
127
124
|
const candidates = roots.flatMap((entry) =>
|
|
128
|
-
fs.existsSync(entry) && fs.statSync(entry).isDirectory() ?
|
|
129
|
-
walkFiles(entry)
|
|
130
|
-
: [entry],
|
|
125
|
+
fs.existsSync(entry) && fs.statSync(entry).isDirectory() ? walkFiles(entry) : [entry],
|
|
131
126
|
);
|
|
132
127
|
|
|
133
128
|
const findings = [];
|
|
@@ -175,8 +170,7 @@ export async function checkFormatting(root, opts = {}) {
|
|
|
175
170
|
// No line or column: Prettier's answer is about the whole
|
|
176
171
|
// file, and #17's rule is to drop a field rather than
|
|
177
172
|
// invent one.
|
|
178
|
-
message:
|
|
179
|
-
"is not formatted; run `content-build format --write` to fix it",
|
|
173
|
+
message: "is not formatted; run `content-build format --write` to fix it",
|
|
180
174
|
});
|
|
181
175
|
}
|
|
182
176
|
} catch (err) {
|
|
@@ -189,9 +183,7 @@ export async function checkFormatting(root, opts = {}) {
|
|
|
189
183
|
const loc = err?.loc?.start ?? err?.loc;
|
|
190
184
|
findings.push({
|
|
191
185
|
file,
|
|
192
|
-
...(Number.isFinite(loc?.line) ?
|
|
193
|
-
{ line: loc.line, column: loc.column }
|
|
194
|
-
: {}),
|
|
186
|
+
...(Number.isFinite(loc?.line) ? { line: loc.line, column: loc.column } : {}),
|
|
195
187
|
severity: "error",
|
|
196
188
|
message: `cannot be parsed: ${String(err?.message ?? err).split("\n")[0]}`,
|
|
197
189
|
});
|
package/engine/region-events.mjs
CHANGED
|
@@ -54,9 +54,7 @@ export const REGION_EVENT_TO_TRIGGER = Object.freeze({
|
|
|
54
54
|
*
|
|
55
55
|
* @type {readonly string[]}
|
|
56
56
|
*/
|
|
57
|
-
export const CURATED_REGION_EVENTS = Object.freeze(
|
|
58
|
-
Object.keys(REGION_EVENT_TO_TRIGGER),
|
|
59
|
-
);
|
|
57
|
+
export const CURATED_REGION_EVENTS = Object.freeze(Object.keys(REGION_EVENT_TO_TRIGGER));
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* Region events SoHL deliberately does **not** forward: the continuous
|
package/engine/scene-levels.mjs
CHANGED
|
@@ -87,9 +87,7 @@ const LEVEL_SPLITTING_CLI = "3.0.3";
|
|
|
87
87
|
*/
|
|
88
88
|
export function compendiumCliVersion() {
|
|
89
89
|
try {
|
|
90
|
-
return createRequire(import.meta.url)(
|
|
91
|
-
"@foundryvtt/foundryvtt-cli/package.json",
|
|
92
|
-
).version;
|
|
90
|
+
return createRequire(import.meta.url)("@foundryvtt/foundryvtt-cli/package.json").version;
|
|
93
91
|
} catch {
|
|
94
92
|
return undefined;
|
|
95
93
|
}
|
|
@@ -128,8 +126,7 @@ function levelIdOf(entry) {
|
|
|
128
126
|
* @returns {string} A phrase naming the entry, never `[object Object]`.
|
|
129
127
|
*/
|
|
130
128
|
function describeEntry(entry, index) {
|
|
131
|
-
if (isInlineLevel(entry))
|
|
132
|
-
return `\`levels[${index}]\` is ${describeShape(entry)}`;
|
|
129
|
+
if (isInlineLevel(entry)) return `\`levels[${index}]\` is ${describeShape(entry)}`;
|
|
133
130
|
return `\`levels[${index}]\` is ${JSON.stringify(entry) ?? String(entry)}`;
|
|
134
131
|
}
|
|
135
132
|
|
|
@@ -234,15 +231,11 @@ function describeRemedy(cliVersion) {
|
|
|
234
231
|
* @returns {string} One report covering every affected scene.
|
|
235
232
|
*/
|
|
236
233
|
function describeWholesaleLoss(declaring, cliVersion) {
|
|
237
|
-
const inline = declaring.some(([, scene]) =>
|
|
238
|
-
declaredLevels(scene).some(isInlineLevel),
|
|
239
|
-
);
|
|
234
|
+
const inline = declaring.some(([, scene]) => declaredLevels(scene).some(isInlineLevel));
|
|
240
235
|
const list = declaring
|
|
241
236
|
.map(([sceneId, scene]) => {
|
|
242
237
|
const entries = declaredLevels(scene)
|
|
243
|
-
.map((entry) =>
|
|
244
|
-
isInlineLevel(entry) ? describeShape(entry) : `"${entry}"`,
|
|
245
|
-
)
|
|
238
|
+
.map((entry) => (isInlineLevel(entry) ? describeShape(entry) : `"${entry}"`))
|
|
246
239
|
.join(", ");
|
|
247
240
|
return `${nameScene(sceneId, scene)} → ${entries}`;
|
|
248
241
|
})
|
|
@@ -329,9 +322,7 @@ export function checkSceneLevels(records, { cliVersion } = {}) {
|
|
|
329
322
|
// No scene in the pack has a Level record: one fact about the compile,
|
|
330
323
|
// which saying per scene would bury. Scenes declaring nothing are not
|
|
331
324
|
// covered by it, so they are still judged individually below.
|
|
332
|
-
const declaring = scenes.filter(
|
|
333
|
-
([, scene]) => declaredLevels(scene).length,
|
|
334
|
-
);
|
|
325
|
+
const declaring = scenes.filter(([, scene]) => declaredLevels(scene).length);
|
|
335
326
|
const wholesale = levelKeys.size === 0 && declaring.length > 0;
|
|
336
327
|
if (wholesale) problems.push(describeWholesaleLoss(declaring, cliVersion));
|
|
337
328
|
|
|
@@ -357,9 +348,7 @@ export function checkSceneLevels(records, { cliVersion } = {}) {
|
|
|
357
348
|
`${LEVEL_SPLITTING_CLI} writes.`,
|
|
358
349
|
);
|
|
359
350
|
} else if (!id) {
|
|
360
|
-
broken.push(
|
|
361
|
-
`${where}: ${describeEntry(entry, index)}, not a Level id.`,
|
|
362
|
-
);
|
|
351
|
+
broken.push(`${where}: ${describeEntry(entry, index)}, not a Level id.`);
|
|
363
352
|
} else if (!levelKeys.has(`${sceneId}.${id}`)) {
|
|
364
353
|
broken.push(
|
|
365
354
|
`${where} lists level "${id}", but no record exists at ` +
|
|
@@ -372,16 +361,13 @@ export function checkSceneLevels(records, { cliVersion } = {}) {
|
|
|
372
361
|
// A broken entry is already reported above; only the declaration
|
|
373
362
|
// itself is judged here, so nothing is reported twice.
|
|
374
363
|
if (broken.length) problems.push(...broken);
|
|
375
|
-
else
|
|
376
|
-
problems.push(...checkDeclaredLevels(scene ?? {}, declared, where));
|
|
364
|
+
else problems.push(...checkDeclaredLevels(scene ?? {}, declared, where));
|
|
377
365
|
}
|
|
378
366
|
|
|
379
367
|
for (const adventure of adventures) {
|
|
380
368
|
const inline = Array.isArray(adventure?.scenes) ? adventure.scenes : [];
|
|
381
369
|
for (const scene of inline) {
|
|
382
|
-
const levelIds = declaredLevels(scene).map(
|
|
383
|
-
(level) => levelIdOf(level) ?? level,
|
|
384
|
-
);
|
|
370
|
+
const levelIds = declaredLevels(scene).map((level) => levelIdOf(level) ?? level);
|
|
385
371
|
const where =
|
|
386
372
|
`Adventure "${adventure?.name ?? adventure?._id}" scene ` +
|
|
387
373
|
`"${scene?.name ?? scene?._id}"`;
|
package/engine/scenes.mjs
CHANGED
|
@@ -51,25 +51,14 @@ import fs from "fs";
|
|
|
51
51
|
import path from "path";
|
|
52
52
|
import log from "loglevel";
|
|
53
53
|
|
|
54
|
-
import {
|
|
55
|
-
walkMarkdownTree,
|
|
56
|
-
sohlField,
|
|
57
|
-
resolveName,
|
|
58
|
-
slugify,
|
|
59
|
-
defaultStats,
|
|
60
|
-
} from "./helpers.mjs";
|
|
54
|
+
import { walkMarkdownTree, sohlField, resolveName, slugify, defaultStats } from "./helpers.mjs";
|
|
61
55
|
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
62
56
|
import { buildJournalEntry, splitPages, journalPageId } from "./journals.mjs";
|
|
63
57
|
import { compendiumUuid, makeId, packForType } from "./ids.mjs";
|
|
64
58
|
import { packRouter } from "./pack-router.mjs";
|
|
65
59
|
import { foundryPackageId } from "./content-package.mjs";
|
|
66
60
|
import { itemDocEntryId } from "./item-docs.mjs";
|
|
67
|
-
import {
|
|
68
|
-
behaviorDocId,
|
|
69
|
-
buildScene,
|
|
70
|
-
isMapType,
|
|
71
|
-
regionDocId,
|
|
72
|
-
} from "./map-notes.mjs";
|
|
61
|
+
import { behaviorDocId, buildScene, isMapType, regionDocId } from "./map-notes.mjs";
|
|
73
62
|
|
|
74
63
|
/**
|
|
75
64
|
* Every SoHL action name this build knows about, for the `action:` warning on a
|
|
@@ -158,9 +147,7 @@ export class Scenes extends BasePackCompiler {
|
|
|
158
147
|
}) {
|
|
159
148
|
super({ contentBase, dest, folderResolver });
|
|
160
149
|
if (!companionDests.adventures) {
|
|
161
|
-
throw new Error(
|
|
162
|
-
"Scenes compiler requires an `adventures` companion destination",
|
|
163
|
-
);
|
|
150
|
+
throw new Error("Scenes compiler requires an `adventures` companion destination");
|
|
164
151
|
}
|
|
165
152
|
Object.defineProperty(this, "adventureDir", {
|
|
166
153
|
value: companionDests.adventures,
|
|
@@ -188,30 +175,21 @@ export class Scenes extends BasePackCompiler {
|
|
|
188
175
|
#collect() {
|
|
189
176
|
const maps = [];
|
|
190
177
|
const effectsByAddress = new Map();
|
|
191
|
-
for (const { frontmatter: fm, body, absPath } of walkMarkdownTree(
|
|
192
|
-
this.contentBase,
|
|
193
|
-
)) {
|
|
178
|
+
for (const { frontmatter: fm, body, absPath } of walkMarkdownTree(this.contentBase)) {
|
|
194
179
|
// No retired-field test: this pass's own walk — the shared compile
|
|
195
180
|
// loop — is where a note still declaring `package:` (#56) or
|
|
196
181
|
// `draft:` (#69) is reported, once. Repeating either check here
|
|
197
182
|
// would double the diagnostic or throw past it. A refused note is
|
|
198
183
|
// indexed and then never compiled, so it reaches no document.
|
|
199
184
|
if (!fm || !fm.id) continue;
|
|
200
|
-
if (
|
|
201
|
-
fm.shortcode &&
|
|
202
|
-
Array.isArray(fm.effects) &&
|
|
203
|
-
fm.effects.length
|
|
204
|
-
) {
|
|
185
|
+
if (fm.shortcode && Array.isArray(fm.effects) && fm.effects.length) {
|
|
205
186
|
effectsByAddress.set(`${fm.type}-${fm.shortcode}`, {
|
|
206
187
|
id: fm.id,
|
|
207
188
|
type: fm.type,
|
|
208
189
|
// Where the owning item landed, so a region behaviour's
|
|
209
190
|
// effect reference addresses the right pack when a
|
|
210
191
|
// repository ships several of one type (#1566).
|
|
211
|
-
pack: packRouter().resolveOrNull(
|
|
212
|
-
fm,
|
|
213
|
-
packForType(fm.type).docType,
|
|
214
|
-
),
|
|
192
|
+
pack: packRouter().resolveOrNull(fm, packForType(fm.type).docType),
|
|
215
193
|
effects: fm.effects,
|
|
216
194
|
});
|
|
217
195
|
}
|
|
@@ -239,17 +217,13 @@ export class Scenes extends BasePackCompiler {
|
|
|
239
217
|
throw new Error(`Map note missing shortcode: ${absPath}`);
|
|
240
218
|
}
|
|
241
219
|
if (index.has(fm.shortcode)) {
|
|
242
|
-
throw new Error(
|
|
243
|
-
`Two map notes share the shortcode "${fm.shortcode}"`,
|
|
244
|
-
);
|
|
220
|
+
throw new Error(`Two map notes share the shortcode "${fm.shortcode}"`);
|
|
245
221
|
}
|
|
246
222
|
const regions = new Map();
|
|
247
223
|
for (const [key, spec] of Object.entries(fm.sohl?.regions ?? {})) {
|
|
248
224
|
const id = regionDocId(fm.id, key, spec?._id);
|
|
249
225
|
const behaviors = new Map();
|
|
250
|
-
for (const [bKey, bSpec] of Object.entries(
|
|
251
|
-
spec?.behaviors ?? {},
|
|
252
|
-
)) {
|
|
226
|
+
for (const [bKey, bSpec] of Object.entries(spec?.behaviors ?? {})) {
|
|
253
227
|
behaviors.set(bKey, behaviorDocId(id, bKey, bSpec?._id));
|
|
254
228
|
}
|
|
255
229
|
regions.set(key, { id, behaviors });
|
|
@@ -283,9 +257,7 @@ export class Scenes extends BasePackCompiler {
|
|
|
283
257
|
const mapKey = addr.map ?? selfShortcode;
|
|
284
258
|
const target = index.get(mapKey);
|
|
285
259
|
if (!target) {
|
|
286
|
-
throw new Error(
|
|
287
|
-
`${label}: no map note has the shortcode "${mapKey}"`,
|
|
288
|
-
);
|
|
260
|
+
throw new Error(`${label}: no map note has the shortcode "${mapKey}"`);
|
|
289
261
|
}
|
|
290
262
|
const region = target.regions.get(addr.region);
|
|
291
263
|
if (!region) {
|
|
@@ -305,9 +277,7 @@ export class Scenes extends BasePackCompiler {
|
|
|
305
277
|
resolveBehaviorRef: (addr, label) => {
|
|
306
278
|
const { target, region } = lookupRegion(addr, label);
|
|
307
279
|
if (!addr.behavior) {
|
|
308
|
-
throw new Error(
|
|
309
|
-
`${label}: a behaviour reference is {map, region, behavior}`,
|
|
310
|
-
);
|
|
280
|
+
throw new Error(`${label}: a behaviour reference is {map, region, behavior}`);
|
|
311
281
|
}
|
|
312
282
|
const behaviorId = region.behaviors.get(addr.behavior);
|
|
313
283
|
if (!behaviorId) {
|
|
@@ -317,8 +287,7 @@ export class Scenes extends BasePackCompiler {
|
|
|
317
287
|
);
|
|
318
288
|
}
|
|
319
289
|
return (
|
|
320
|
-
`Scene.${target.sceneId}.Region.${region.id}` +
|
|
321
|
-
`.RegionBehavior.${behaviorId}`
|
|
290
|
+
`Scene.${target.sceneId}.Region.${region.id}` + `.RegionBehavior.${behaviorId}`
|
|
322
291
|
);
|
|
323
292
|
},
|
|
324
293
|
resolveEffectRef: (addr, label) => {
|
|
@@ -420,8 +389,7 @@ export class Scenes extends BasePackCompiler {
|
|
|
420
389
|
// default JournalEntry pack, not in whichever Scene pack the map
|
|
421
390
|
// itself was routed to (#1566).
|
|
422
391
|
journalPack: packRouter().defaultOf("JournalEntry"),
|
|
423
|
-
pageIds:
|
|
424
|
-
hasBody ? this.#pageIds(markdown, entryId, name) : new Map(),
|
|
392
|
+
pageIds: hasBody ? this.#pageIds(markdown, entryId, name) : new Map(),
|
|
425
393
|
knownActions: this.knownActions,
|
|
426
394
|
warnings,
|
|
427
395
|
...this.#resolvers(this.index, this.effectsByAddress, fm.shortcode),
|
|
@@ -489,9 +457,7 @@ export class Scenes extends BasePackCompiler {
|
|
|
489
457
|
|
|
490
458
|
/** @inheritdoc */
|
|
491
459
|
reportCompiled(stats) {
|
|
492
|
-
log.info(
|
|
493
|
-
`Compiled ${stats.compiled} scene(s) and ${this.adventureCount} adventure(s)`,
|
|
494
|
-
);
|
|
460
|
+
log.info(`Compiled ${stats.compiled} scene(s) and ${this.adventureCount} adventure(s)`);
|
|
495
461
|
}
|
|
496
462
|
|
|
497
463
|
/**
|