@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/bin/content-build.mjs +44 -118
  3. package/bin/package-build.mjs +27 -69
  4. package/bin/report.mjs +1 -2
  5. package/bundle.mjs +2 -10
  6. package/config.mjs +31 -106
  7. package/container.mjs +13 -57
  8. package/content-config.mjs +45 -164
  9. package/coverage.mjs +14 -55
  10. package/deploy.mjs +4 -13
  11. package/e2e.mjs +16 -55
  12. package/engine/address-diff.mjs +1 -4
  13. package/engine/base-compiler.mjs +10 -28
  14. package/engine/code-fences.mjs +4 -13
  15. package/engine/compendiums.mjs +13 -37
  16. package/engine/content-address.mjs +2 -6
  17. package/engine/content-links.mjs +13 -44
  18. package/engine/content-lint.mjs +4 -15
  19. package/engine/content-slug.mjs +2 -6
  20. package/engine/content-tables.mjs +26 -79
  21. package/engine/diagnostics.mjs +4 -15
  22. package/engine/field-reference.mjs +6 -20
  23. package/engine/field-spec.mjs +1 -3
  24. package/engine/foreign-catalog.mjs +7 -22
  25. package/engine/foreign-manifests.mjs +1 -4
  26. package/engine/frontmatter-lint.mjs +6 -18
  27. package/engine/frontmatter.mjs +3 -8
  28. package/engine/generate.mjs +4 -16
  29. package/engine/helpers.mjs +13 -52
  30. package/engine/homepage.mjs +4 -15
  31. package/engine/ids.mjs +3 -12
  32. package/engine/item-registry.mjs +2 -6
  33. package/engine/journals.mjs +4 -14
  34. package/engine/kb-manifest.mjs +5 -17
  35. package/engine/macros.mjs +2 -10
  36. package/engine/manifest-emit.mjs +6 -17
  37. package/engine/map-notes.mjs +19 -69
  38. package/engine/note-package.mjs +1 -4
  39. package/engine/pack-config.mjs +17 -38
  40. package/engine/pack-router.mjs +1 -2
  41. package/engine/prose-config.mjs +20 -4
  42. package/engine/prose-lint.mjs +6 -14
  43. package/engine/region-events.mjs +1 -3
  44. package/engine/scene-levels.mjs +8 -22
  45. package/engine/scenes.mjs +13 -47
  46. package/engine/schema-check.mjs +1 -4
  47. package/engine/schema-extract.mjs +70 -45
  48. package/engine/site-build.mjs +12 -37
  49. package/engine/site-index.mjs +3 -15
  50. package/engine/web-wikilinks.mjs +4 -13
  51. package/engine/wikilinks.mjs +115 -167
  52. package/index.mjs +1 -5
  53. package/lang.mjs +1 -3
  54. package/manifest.mjs +10 -37
  55. package/markdownlint-config.mjs +1 -5
  56. package/package.json +1 -1
  57. package/sohl/actors.mjs +10 -40
  58. package/sohl/being-info.mjs +3 -6
  59. package/sohl/index.mjs +1 -6
  60. package/sohl/item-builders.mjs +1 -3
  61. package/sohl/item-fields.mjs +16 -34
  62. package/sohl/items.mjs +1 -3
  63. package/sohl/kb-passes.mjs +29 -39
  64. package/sohl/skill-base.mjs +7 -23
  65. package/stage.mjs +3 -13
  66. package/templates.mjs +4 -15
  67. package/types/bundle.d.mts +1 -1
  68. package/types/container.d.mts +2 -2
  69. package/types/coverage.d.mts +1 -1
  70. package/types/e2e.d.mts +4 -4
  71. package/types/engine/generate.d.mts +1 -1
  72. package/types/engine/helpers.d.mts +1 -1
  73. package/types/engine/schema-extract.d.mts +1 -1
  74. package/types/engine/site-index.d.mts +1 -1
  75. package/types/manifest.d.mts +1 -1
@@ -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,
@@ -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();
@@ -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
- path.join(dir, name),
135
- ).filter((candidate) => fs.existsSync(candidate));
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
- .systemId;
280
- const declaredSystems = /** @type {Record<string, unknown>} */ (
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
- /** @type {Record<string, {compatibility?: {verified?: string}}>} */ (
293
- input.systems ?? {}
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
  }
@@ -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.
@@ -46,7 +46,25 @@
46
46
  * @type {Readonly<object>}
47
47
  */
48
48
  export const PRETTIER_BASE = Object.freeze({
49
- printWidth: 80,
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
  /**
@@ -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
- (file) => fs.existsSync(file),
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
  });
@@ -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
@@ -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
  /**