@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/wikilinks.mjs
CHANGED
|
@@ -82,13 +82,7 @@
|
|
|
82
82
|
|
|
83
83
|
import crypto from "crypto";
|
|
84
84
|
|
|
85
|
-
import {
|
|
86
|
-
compendiumUuid,
|
|
87
|
-
ITEM_PACK,
|
|
88
|
-
packForType,
|
|
89
|
-
pageUuid,
|
|
90
|
-
PACK_BY_TYPE,
|
|
91
|
-
} from "./ids.mjs";
|
|
85
|
+
import { compendiumUuid, ITEM_PACK, packForType, pageUuid, PACK_BY_TYPE } from "./ids.mjs";
|
|
92
86
|
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
93
87
|
import { replaceOutsideCode } from "./code-fences.mjs";
|
|
94
88
|
// The syntax lives in `./wikilink-syntax.mjs`, so the web resolver and this
|
|
@@ -194,11 +188,7 @@ export function readQualifier(target, types, packages) {
|
|
|
194
188
|
|
|
195
189
|
const slash = target.lastIndexOf("/");
|
|
196
190
|
if (slash > 0) {
|
|
197
|
-
const read = readTypeAndCode(
|
|
198
|
-
target.slice(0, slash),
|
|
199
|
-
target.slice(slash + 1),
|
|
200
|
-
types,
|
|
201
|
-
);
|
|
191
|
+
const read = readTypeAndCode(target.slice(0, slash), target.slice(slash + 1), types);
|
|
202
192
|
// A slash means qualified whether or not the type is real.
|
|
203
193
|
return read ?? { reason: "unknown-type" };
|
|
204
194
|
}
|
|
@@ -206,11 +196,7 @@ export function readQualifier(target, types, packages) {
|
|
|
206
196
|
const hyphen = target.indexOf("-");
|
|
207
197
|
if (hyphen > 0) {
|
|
208
198
|
// A hyphen qualifies only on a known type; otherwise it is part of a name.
|
|
209
|
-
return readTypeAndCode(
|
|
210
|
-
target.slice(0, hyphen),
|
|
211
|
-
target.slice(hyphen + 1),
|
|
212
|
-
types,
|
|
213
|
-
);
|
|
199
|
+
return readTypeAndCode(target.slice(0, hyphen), target.slice(hyphen + 1), types);
|
|
214
200
|
}
|
|
215
201
|
return null;
|
|
216
202
|
}
|
|
@@ -313,23 +299,14 @@ export function buildWikilinkIndex(docs, packageId, foreign, contentPackage) {
|
|
|
313
299
|
// An item's prose compiles into a separate JournalEntry, addressed
|
|
314
300
|
// by the virtual `doc<type>` qualifier. Its id is derived from the
|
|
315
301
|
// item's, so its address is knowable here too.
|
|
316
|
-
docUuid: compendiumUuid(
|
|
317
|
-
packageId,
|
|
318
|
-
"doc",
|
|
319
|
-
itemDocEntryId(d.id),
|
|
320
|
-
d.docPack,
|
|
321
|
-
),
|
|
302
|
+
docUuid: compendiumUuid(packageId, "doc", itemDocEntryId(d.id), d.docPack),
|
|
322
303
|
});
|
|
323
304
|
|
|
324
|
-
if (d.shortcode)
|
|
325
|
-
byShortcode.set(`${norm(d.type)}/${norm(d.shortcode)}`, d);
|
|
305
|
+
if (d.shortcode) byShortcode.set(`${norm(d.type)}/${norm(d.shortcode)}`, d);
|
|
326
306
|
for (const a of d.aliases ?? []) {
|
|
327
307
|
const key = `${norm(d.type)}|${norm(a)}`;
|
|
328
308
|
// Second claimant poisons the alias: it can no longer be resolved.
|
|
329
|
-
byAlias.set(
|
|
330
|
-
key,
|
|
331
|
-
byAlias.has(key) && byAlias.get(key) !== d ? null : d,
|
|
332
|
-
);
|
|
309
|
+
byAlias.set(key, byAlias.has(key) && byAlias.get(key) !== d ? null : d);
|
|
333
310
|
// Every claimant is kept alongside, because poisoning the alias
|
|
334
311
|
// discards exactly the information needed to report the problem.
|
|
335
312
|
// The note that *cites* an ambiguous alias is innocent — whoever
|
|
@@ -411,11 +388,7 @@ function findForeign(index, read) {
|
|
|
411
388
|
const wanted = norm(read.itemDoc ? `doc${read.type}` : read.type);
|
|
412
389
|
const shortcode = norm(read.shortcode);
|
|
413
390
|
if (read.package) {
|
|
414
|
-
return (
|
|
415
|
-
index.foreign.get(
|
|
416
|
-
`${read.package}-${wanted}-${shortcode}`.toLowerCase(),
|
|
417
|
-
) ?? null
|
|
418
|
-
);
|
|
391
|
+
return index.foreign.get(`${read.package}-${wanted}-${shortcode}`.toLowerCase()) ?? null;
|
|
419
392
|
}
|
|
420
393
|
const hits = [];
|
|
421
394
|
for (const [key, v] of index.foreign) {
|
|
@@ -490,159 +463,134 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
|
|
|
490
463
|
// `offset` is the third replacer argument because the pattern has exactly
|
|
491
464
|
// one capture group. It is what makes two identical unresolved links on
|
|
492
465
|
// one note tellable apart, and a position reportable at all (#17).
|
|
493
|
-
const out = replaceOutsideCode(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
// the line somewhere else (#113).
|
|
505
|
-
let text = labelled ? (authoredLabel(parsed) ?? "") : parsed.inner;
|
|
506
|
-
const slug = parsed.anchor || null;
|
|
466
|
+
const out = replaceOutsideCode(markdown, WIKILINK, (all, rawInner, offset) => {
|
|
467
|
+
const parsed = parseWikilink(rawInner);
|
|
468
|
+
const { labelled } = parsed;
|
|
469
|
+
let target = parsed.target;
|
|
470
|
+
// An unlabelled link shows its interior verbatim, anchor included;
|
|
471
|
+
// a labelled one shows its label. An *empty* label is not a label
|
|
472
|
+
// — `[[x|]]` means "show the target's name" — and that reading
|
|
473
|
+
// comes from {@link authoredLabel} so the web resolver cannot draw
|
|
474
|
+
// the line somewhere else (#113).
|
|
475
|
+
let text = labelled ? (authoredLabel(parsed) ?? "") : parsed.inner;
|
|
476
|
+
const slug = parsed.anchor || null;
|
|
507
477
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
478
|
+
// Resolve the document: same-page (empty target), type-shortcode, or alias.
|
|
479
|
+
let doc;
|
|
480
|
+
// Set when the qualifier was the virtual `doc<type>` form, so the UUID
|
|
481
|
+
// is built against the item doc entry rather than the item itself.
|
|
482
|
+
let itemDoc = false;
|
|
483
|
+
// Set when the target was read as `type-shortcode` — an address rather
|
|
484
|
+
// than prose, which decides what an unlabelled link shows (#1409).
|
|
485
|
+
let addressed = false;
|
|
486
|
+
// Kept for the foreign fallback below, which needs the parsed address.
|
|
487
|
+
let qualifiedRead = null;
|
|
488
|
+
if (target === "" && slug) {
|
|
489
|
+
doc = { type, id, pack, docPack };
|
|
490
|
+
} else {
|
|
491
|
+
const qualified = readQualifier(target, index.types, index.packages);
|
|
492
|
+
qualifiedRead = qualified;
|
|
493
|
+
if (qualified?.reason) {
|
|
494
|
+
unresolved.push({
|
|
495
|
+
link: all,
|
|
522
496
|
target,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
|
|
497
|
+
offset,
|
|
498
|
+
reason: qualified.reason,
|
|
499
|
+
});
|
|
500
|
+
return unresolvedLink(text || target, target);
|
|
501
|
+
}
|
|
502
|
+
if (qualified) {
|
|
503
|
+
addressed = true;
|
|
504
|
+
itemDoc = qualified.itemDoc;
|
|
505
|
+
doc = index.byShortcode.get(`${qualified.type}/${qualified.shortcode}`);
|
|
506
|
+
} else {
|
|
507
|
+
const aliasKey = `${norm(type)}|${norm(target)}`;
|
|
508
|
+
const hit = index.byAlias.get(aliasKey);
|
|
509
|
+
if (hit === null) {
|
|
528
510
|
unresolved.push({
|
|
529
511
|
link: all,
|
|
530
512
|
target,
|
|
531
513
|
offset,
|
|
532
|
-
reason:
|
|
514
|
+
reason: "ambiguous",
|
|
515
|
+
// Who claimed it, so the report can name the collision
|
|
516
|
+
// rather than the note that merely cites it (#13).
|
|
517
|
+
candidates: (index.aliasClaims?.get(aliasKey) ?? []).map((d) => ({
|
|
518
|
+
type: d.type,
|
|
519
|
+
shortcode: d.shortcode,
|
|
520
|
+
name: d.name,
|
|
521
|
+
})),
|
|
533
522
|
});
|
|
534
523
|
return unresolvedLink(text || target, target);
|
|
535
524
|
}
|
|
536
|
-
|
|
537
|
-
addressed = true;
|
|
538
|
-
itemDoc = qualified.itemDoc;
|
|
539
|
-
doc = index.byShortcode.get(
|
|
540
|
-
`${qualified.type}/${qualified.shortcode}`,
|
|
541
|
-
);
|
|
542
|
-
} else {
|
|
543
|
-
const aliasKey = `${norm(type)}|${norm(target)}`;
|
|
544
|
-
const hit = index.byAlias.get(aliasKey);
|
|
545
|
-
if (hit === null) {
|
|
546
|
-
unresolved.push({
|
|
547
|
-
link: all,
|
|
548
|
-
target,
|
|
549
|
-
offset,
|
|
550
|
-
reason: "ambiguous",
|
|
551
|
-
// Who claimed it, so the report can name the collision
|
|
552
|
-
// rather than the note that merely cites it (#13).
|
|
553
|
-
candidates: (
|
|
554
|
-
index.aliasClaims?.get(aliasKey) ?? []
|
|
555
|
-
).map((d) => ({
|
|
556
|
-
type: d.type,
|
|
557
|
-
shortcode: d.shortcode,
|
|
558
|
-
name: d.name,
|
|
559
|
-
})),
|
|
560
|
-
});
|
|
561
|
-
return unresolvedLink(text || target, target);
|
|
562
|
-
}
|
|
563
|
-
doc = hit;
|
|
564
|
-
}
|
|
525
|
+
doc = hit;
|
|
565
526
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
unresolved.push({
|
|
578
|
-
link: all,
|
|
579
|
-
target,
|
|
580
|
-
offset,
|
|
581
|
-
reason: "unknown-anchor",
|
|
582
|
-
addressed: true,
|
|
583
|
-
});
|
|
584
|
-
return unresolvedLink(text || target, target);
|
|
527
|
+
}
|
|
528
|
+
if (!doc) {
|
|
529
|
+
// Nothing local answers. A foreign package may publish this
|
|
530
|
+
// address, in which case the manifest hands back a complete UUID —
|
|
531
|
+
// including, for a section link, the anchor's own — so nothing is
|
|
532
|
+
// derived here.
|
|
533
|
+
const hit = findForeign(index, qualifiedRead);
|
|
534
|
+
if (hit) {
|
|
535
|
+
const uuid = slug ? hit.anchors?.[slug] : hit.uuid;
|
|
536
|
+
if (uuid) {
|
|
537
|
+
return `@UUID[${uuid}]{${text || hit.name || target}}`;
|
|
585
538
|
}
|
|
586
539
|
unresolved.push({
|
|
587
540
|
link: all,
|
|
588
541
|
target,
|
|
589
542
|
offset,
|
|
590
|
-
reason: "unknown",
|
|
591
|
-
|
|
592
|
-
// package it could name is either built here or vendored, so
|
|
593
|
-
// there is no third possibility left. A bare alias is not — it
|
|
594
|
-
// may simply be prose.
|
|
595
|
-
addressed: !!qualifiedRead && !qualifiedRead.reason,
|
|
543
|
+
reason: "unknown-anchor",
|
|
544
|
+
addressed: true,
|
|
596
545
|
});
|
|
597
546
|
return unresolvedLink(text || target, target);
|
|
598
547
|
}
|
|
548
|
+
unresolved.push({
|
|
549
|
+
link: all,
|
|
550
|
+
target,
|
|
551
|
+
offset,
|
|
552
|
+
reason: "unknown",
|
|
553
|
+
// A *qualified* address that resolves nowhere is a typo: every
|
|
554
|
+
// package it could name is either built here or vendored, so
|
|
555
|
+
// there is no third possibility left. A bare alias is not — it
|
|
556
|
+
// may simply be prose.
|
|
557
|
+
addressed: !!qualifiedRead && !qualifiedRead.reason,
|
|
558
|
+
});
|
|
559
|
+
return unresolvedLink(text || target, target);
|
|
560
|
+
}
|
|
599
561
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
562
|
+
// With no explicit label, a *qualified* target has no prose to show — a
|
|
563
|
+
// shortcode is an address, not display text — so the document's own name
|
|
564
|
+
// stands in (#1409). A bare `[[Text]]` is already the prose the author
|
|
565
|
+
// wrote, and substituting the canonical name there would rewrite the
|
|
566
|
+
// sentence ("worsens the [[Shock State]]" must not render as "Shock").
|
|
567
|
+
// The knowledgebase build reads the same authored link the same way.
|
|
568
|
+
if (!text || (!labelled && addressed)) text = doc.name ?? target;
|
|
607
569
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
itemDoc || packForType(doc.type).docType === "JournalEntry";
|
|
633
|
-
// A JournalEntry link opens a journal — at its first page, or at the
|
|
634
|
-
// page an anchor names. An Item or Actor link opens that document's
|
|
635
|
-
// *sheet*, which has no sections, so the anchor has nothing to address
|
|
636
|
-
// and is dropped. Forging a JournalEntryPage id onto a document that
|
|
637
|
-
// can never hold one is what made such links dead-end (#1362); an
|
|
638
|
-
// item's pages are addressed through its `doc<type>` counterpart.
|
|
639
|
-
const uuid =
|
|
640
|
-
slug && isJournal ?
|
|
641
|
-
pageUuid(entryUuid, anchorPageId(entryId, slug))
|
|
642
|
-
: entryUuid;
|
|
643
|
-
return `@UUID[${uuid}]{${text}}`;
|
|
644
|
-
},
|
|
645
|
-
);
|
|
570
|
+
// Both addresses were computed when the target was indexed. An item
|
|
571
|
+
// doc lives in the journals pack under its own derived entry id, and
|
|
572
|
+
// its pages hash against *that* id — not the item's.
|
|
573
|
+
//
|
|
574
|
+
// The one target with no index entry is the note itself: a `[[#slug]]`
|
|
575
|
+
// self-link is resolved from the source's own type and id, which the
|
|
576
|
+
// caller supplied, so it is addressed the same way here.
|
|
577
|
+
const addresses = index.uuidByDoc.get(doc) ?? {
|
|
578
|
+
uuid: compendiumUuid(index.packageId, doc.type, doc.id, doc.pack),
|
|
579
|
+
docUuid: compendiumUuid(index.packageId, "doc", itemDocEntryId(doc.id), doc.docPack),
|
|
580
|
+
};
|
|
581
|
+
const entryUuid = itemDoc ? addresses.docUuid : addresses.uuid;
|
|
582
|
+
const entryId = itemDoc ? itemDocEntryId(doc.id) : doc.id;
|
|
583
|
+
const isJournal = itemDoc || packForType(doc.type).docType === "JournalEntry";
|
|
584
|
+
// A JournalEntry link opens a journal — at its first page, or at the
|
|
585
|
+
// page an anchor names. An Item or Actor link opens that document's
|
|
586
|
+
// *sheet*, which has no sections, so the anchor has nothing to address
|
|
587
|
+
// and is dropped. Forging a JournalEntryPage id onto a document that
|
|
588
|
+
// can never hold one is what made such links dead-end (#1362); an
|
|
589
|
+
// item's pages are addressed through its `doc<type>` counterpart.
|
|
590
|
+
const uuid =
|
|
591
|
+
slug && isJournal ? pageUuid(entryUuid, anchorPageId(entryId, slug)) : entryUuid;
|
|
592
|
+
return `@UUID[${uuid}]{${text}}`;
|
|
593
|
+
});
|
|
646
594
|
|
|
647
595
|
return { markdown: out, unresolved };
|
|
648
596
|
}
|
package/index.mjs
CHANGED
|
@@ -42,11 +42,7 @@
|
|
|
42
42
|
// ── Content: notes to compendium packs, site content, link manifests ────────
|
|
43
43
|
|
|
44
44
|
/** The configuration contract a consuming repository declares its build with. */
|
|
45
|
-
export {
|
|
46
|
-
defineConfig,
|
|
47
|
-
PACKAGE_KINDS,
|
|
48
|
-
PACK_DOCUMENT_TYPES,
|
|
49
|
-
} from "./content-config.mjs";
|
|
45
|
+
export { defineConfig, PACKAGE_KINDS, PACK_DOCUMENT_TYPES } from "./content-config.mjs";
|
|
50
46
|
|
|
51
47
|
/** The content pipeline — walking, compiling, linking, emitting. */
|
|
52
48
|
export * as engine from "./engine/index.mjs";
|
package/lang.mjs
CHANGED
|
@@ -116,9 +116,7 @@ export function validateLangSource(raw) {
|
|
|
116
116
|
} catch (err) {
|
|
117
117
|
// Nothing further can be said about a file that does not parse, and
|
|
118
118
|
// guessing at its intended shape would only bury this finding.
|
|
119
|
-
return [
|
|
120
|
-
{ severity: "error", message: `not valid JSON: ${err.message}` },
|
|
121
|
-
];
|
|
119
|
+
return [{ severity: "error", message: `not valid JSON: ${err.message}` }];
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
if (!isRecord(json)) {
|
package/manifest.mjs
CHANGED
|
@@ -81,8 +81,7 @@ export const ARTIFACTS = Object.freeze(["system", "module"]);
|
|
|
81
81
|
* worse than a missing one: Foundry installs it and never offers an update.
|
|
82
82
|
*/
|
|
83
83
|
export function normalizeRepoUrl(repository) {
|
|
84
|
-
const raw =
|
|
85
|
-
typeof repository === "string" ? repository : (repository?.url ?? "");
|
|
84
|
+
const raw = typeof repository === "string" ? repository : (repository?.url ?? "");
|
|
86
85
|
const url = String(raw)
|
|
87
86
|
.trim()
|
|
88
87
|
.replace(/^git\+/, "")
|
|
@@ -177,10 +176,7 @@ const MANIFEST_KEY_ORDER = Object.freeze([
|
|
|
177
176
|
* @returns {object[]} The manifest's `packs` array.
|
|
178
177
|
*/
|
|
179
178
|
export function manifestPacks(config) {
|
|
180
|
-
const flatten = (pack) => [
|
|
181
|
-
pack,
|
|
182
|
-
...(pack.companions ?? []).flatMap(flatten),
|
|
183
|
-
];
|
|
179
|
+
const flatten = (pack) => [pack, ...(pack.companions ?? []).flatMap(flatten)];
|
|
184
180
|
return config.packs.flatMap(flatten).map((pack) => {
|
|
185
181
|
// Foundry requires `system` on ActiveEffect, Actor and Item packs and
|
|
186
182
|
// on no others, so the value is per pack: its own declaration first,
|
|
@@ -205,11 +201,7 @@ export function manifestPacks(config) {
|
|
|
205
201
|
*
|
|
206
202
|
* @type {readonly string[]}
|
|
207
203
|
*/
|
|
208
|
-
const PACK_FOLDERS_PATH = Object.freeze([
|
|
209
|
-
"packageBuild",
|
|
210
|
-
"manifest",
|
|
211
|
-
"packFolders",
|
|
212
|
-
]);
|
|
204
|
+
const PACK_FOLDERS_PATH = Object.freeze(["packageBuild", "manifest", "packFolders"]);
|
|
213
205
|
|
|
214
206
|
/**
|
|
215
207
|
* Every pack name a folder tree names, with the folder that named it.
|
|
@@ -371,8 +363,7 @@ export const BUILD_ONLY_RELATIONSHIP_KEYS = Object.freeze(["itemCatalog"]);
|
|
|
371
363
|
export function publishedRelationships(relationships) {
|
|
372
364
|
const published = {};
|
|
373
365
|
for (const [kind, entries] of Object.entries(relationships)) {
|
|
374
|
-
published[kind] =
|
|
375
|
-
Array.isArray(entries) ? entries.map(withoutBuildKeys) : entries;
|
|
366
|
+
published[kind] = Array.isArray(entries) ? entries.map(withoutBuildKeys) : entries;
|
|
376
367
|
}
|
|
377
368
|
return published;
|
|
378
369
|
}
|
|
@@ -388,9 +379,7 @@ function withoutBuildKeys(entry) {
|
|
|
388
379
|
return entry;
|
|
389
380
|
}
|
|
390
381
|
return Object.fromEntries(
|
|
391
|
-
Object.entries(entry).filter(
|
|
392
|
-
([key]) => !BUILD_ONLY_RELATIONSHIP_KEYS.includes(key),
|
|
393
|
-
),
|
|
382
|
+
Object.entries(entry).filter(([key]) => !BUILD_ONLY_RELATIONSHIP_KEYS.includes(key)),
|
|
394
383
|
);
|
|
395
384
|
}
|
|
396
385
|
|
|
@@ -447,23 +436,18 @@ export function buildManifest({ config, packageJson, artifact, flags }) {
|
|
|
447
436
|
const entry = {
|
|
448
437
|
id: config.requiresSystem,
|
|
449
438
|
type: "system",
|
|
450
|
-
...(declaredSystem?.manifest ?
|
|
451
|
-
{ manifest: declaredSystem.manifest }
|
|
452
|
-
: {}),
|
|
439
|
+
...(declaredSystem?.manifest ? { manifest: declaredSystem.manifest } : {}),
|
|
453
440
|
...(declaredSystem?.compatibility ?
|
|
454
441
|
{
|
|
455
442
|
compatibility: Object.fromEntries(
|
|
456
|
-
Object.entries(declaredSystem.compatibility).filter(
|
|
457
|
-
([, v]) => v != null,
|
|
458
|
-
),
|
|
443
|
+
Object.entries(declaredSystem.compatibility).filter(([, v]) => v != null),
|
|
459
444
|
),
|
|
460
445
|
}
|
|
461
446
|
: {}),
|
|
462
447
|
};
|
|
463
448
|
// An explicit `relationships.systems` still wins, so a repository
|
|
464
449
|
// mid-migration is never told two different things about itself.
|
|
465
|
-
relationships.systems =
|
|
466
|
-
relationships.systems?.length ? relationships.systems : [entry];
|
|
450
|
+
relationships.systems = relationships.systems?.length ? relationships.systems : [entry];
|
|
467
451
|
}
|
|
468
452
|
if (Object.keys(relationships).length) {
|
|
469
453
|
derived.relationships = publishedRelationships(relationships);
|
|
@@ -553,14 +537,7 @@ function reportPackFolders(findings, configFile) {
|
|
|
553
537
|
* @throws {Error} When a `packFolders` entry names a pack the package does not
|
|
554
538
|
* ship. Nothing is written in that case.
|
|
555
539
|
*/
|
|
556
|
-
export async function writeManifest({
|
|
557
|
-
config,
|
|
558
|
-
packageJson,
|
|
559
|
-
artifact,
|
|
560
|
-
outDir,
|
|
561
|
-
flags,
|
|
562
|
-
configFile,
|
|
563
|
-
}) {
|
|
540
|
+
export async function writeManifest({ config, packageJson, artifact, outDir, flags, configFile }) {
|
|
564
541
|
const manifest = buildManifest({ config, packageJson, artifact, flags });
|
|
565
542
|
|
|
566
543
|
const errors = reportPackFolders(
|
|
@@ -583,10 +560,6 @@ export async function writeManifest({
|
|
|
583
560
|
const outPath = path.join(outDir, `${artifact}.json`);
|
|
584
561
|
// Trailing newline: the file is committed to a release archive and read by
|
|
585
562
|
// humans as often as by Foundry.
|
|
586
|
-
await fs.writeFile(
|
|
587
|
-
outPath,
|
|
588
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
589
|
-
"utf8",
|
|
590
|
-
);
|
|
563
|
+
await fs.writeFile(outPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
591
564
|
return { path: outPath, manifest };
|
|
592
565
|
}
|
package/markdownlint-config.mjs
CHANGED
|
@@ -31,11 +31,7 @@
|
|
|
31
31
|
* @module
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import {
|
|
35
|
-
MARKDOWNLINT_CONFIG,
|
|
36
|
-
MARKDOWN_GLOBS,
|
|
37
|
-
MARKDOWN_IGNORES,
|
|
38
|
-
} from "./engine/prose-config.mjs";
|
|
34
|
+
import { MARKDOWNLINT_CONFIG, MARKDOWN_GLOBS, MARKDOWN_IGNORES } from "./engine/prose-config.mjs";
|
|
39
35
|
|
|
40
36
|
export default {
|
|
41
37
|
config: MARKDOWNLINT_CONFIG,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
package/sohl/actors.mjs
CHANGED
|
@@ -151,9 +151,7 @@ function extractBodyAndMovement(fm) {
|
|
|
151
151
|
return {
|
|
152
152
|
body: normalizeBody(sohlField(fm, "body", {})),
|
|
153
153
|
currentMoveMedium: String(sohlField(fm, "currentMoveMedium", "none")),
|
|
154
|
-
movementProfiles: normalizeMovementProfiles(
|
|
155
|
-
sohlField(fm, "movementProfiles", []),
|
|
156
|
-
),
|
|
154
|
+
movementProfiles: normalizeMovementProfiles(sohlField(fm, "movementProfiles", [])),
|
|
157
155
|
};
|
|
158
156
|
}
|
|
159
157
|
|
|
@@ -290,8 +288,7 @@ function extractAnchorSection(body, anchorId) {
|
|
|
290
288
|
if (capturing) captured.push(line);
|
|
291
289
|
continue;
|
|
292
290
|
}
|
|
293
|
-
const h1Match =
|
|
294
|
-
!inCodeBlock ? line.match(/^\s*#\s+(.+?)\s*#*\s*$/) : null;
|
|
291
|
+
const h1Match = !inCodeBlock ? line.match(/^\s*#\s+(.+?)\s*#*\s*$/) : null;
|
|
295
292
|
if (h1Match) {
|
|
296
293
|
const anchor = h1Match[1].match(/\{#([^}]+)\}\s*$/);
|
|
297
294
|
const id = anchor?.[1]?.trim().toLowerCase() || null;
|
|
@@ -383,13 +380,8 @@ export class Actors extends BasePackCompiler {
|
|
|
383
380
|
*/
|
|
384
381
|
async prepare() {
|
|
385
382
|
await super.prepare();
|
|
386
|
-
this.itemsMap = loadItemsMap(
|
|
387
|
-
|
|
388
|
-
this.foreignSourceDirs,
|
|
389
|
-
);
|
|
390
|
-
log.info(
|
|
391
|
-
`Loaded ${this.itemsMap.size} predefined items for actor resolution`,
|
|
392
|
-
);
|
|
383
|
+
this.itemsMap = loadItemsMap(this.itemsSourceDirs, this.foreignSourceDirs);
|
|
384
|
+
log.info(`Loaded ${this.itemsMap.size} predefined items for actor resolution`);
|
|
393
385
|
}
|
|
394
386
|
|
|
395
387
|
/**
|
|
@@ -421,22 +413,12 @@ export class Actors extends BasePackCompiler {
|
|
|
421
413
|
* `(actorId, type, shortcode, indexKey)` so re-exports are stable.
|
|
422
414
|
* Returns null if the descriptor cannot be resolved.
|
|
423
415
|
*/
|
|
424
|
-
resolveEmbedded(
|
|
425
|
-
itemsMap,
|
|
426
|
-
actorId,
|
|
427
|
-
type,
|
|
428
|
-
shortcode,
|
|
429
|
-
overlay,
|
|
430
|
-
indexKey,
|
|
431
|
-
ctx,
|
|
432
|
-
) {
|
|
416
|
+
resolveEmbedded(itemsMap, actorId, type, shortcode, overlay, indexKey, ctx) {
|
|
433
417
|
let base = null;
|
|
434
418
|
if (shortcode) {
|
|
435
419
|
base = itemsMap.get(`${type}:${shortcode}`);
|
|
436
420
|
if (!base) {
|
|
437
|
-
this.noteError(
|
|
438
|
-
`${ctx}: no predefined item for "${type}:${shortcode}"`,
|
|
439
|
-
);
|
|
421
|
+
this.noteError(`${ctx}: no predefined item for "${type}:${shortcode}"`);
|
|
440
422
|
this.errorCount++;
|
|
441
423
|
return null;
|
|
442
424
|
}
|
|
@@ -452,10 +434,7 @@ export class Actors extends BasePackCompiler {
|
|
|
452
434
|
}
|
|
453
435
|
const merged = overlay ? deepMerge(base, overlay) : base;
|
|
454
436
|
merged.type = type;
|
|
455
|
-
merged._id = makeId(
|
|
456
|
-
actorId,
|
|
457
|
-
`${type}:${shortcode || merged.name}:${indexKey}`,
|
|
458
|
-
);
|
|
437
|
+
merged._id = makeId(actorId, `${type}:${shortcode || merged.name}:${indexKey}`);
|
|
459
438
|
// Foundry's pack compiler flattens the document hierarchy into LevelDB,
|
|
460
439
|
// storing each embedded document under its own `_key`. Embedded items
|
|
461
440
|
// therefore need a hierarchical key, as do any effects they carry
|
|
@@ -499,9 +478,7 @@ export class Actors extends BasePackCompiler {
|
|
|
499
478
|
if (Array.isArray(sohlItems)) {
|
|
500
479
|
sohlItems.forEach((entry, index) => {
|
|
501
480
|
if (!entry || typeof entry !== "object") {
|
|
502
|
-
this.noteError(
|
|
503
|
-
`${ctx}: sohl.items[${index}] is not an object`,
|
|
504
|
-
);
|
|
481
|
+
this.noteError(`${ctx}: sohl.items[${index}] is not an object`);
|
|
505
482
|
this.errorCount++;
|
|
506
483
|
return;
|
|
507
484
|
}
|
|
@@ -556,10 +533,7 @@ export class Actors extends BasePackCompiler {
|
|
|
556
533
|
*/
|
|
557
534
|
openUnopenedSkills(items, ctx) {
|
|
558
535
|
const skills = items.filter(
|
|
559
|
-
(item) =>
|
|
560
|
-
item.type === "skill" &&
|
|
561
|
-
item.system &&
|
|
562
|
-
item.system.masteryLevelBase == null,
|
|
536
|
+
(item) => item.type === "skill" && item.system && item.system.masteryLevelBase == null,
|
|
563
537
|
);
|
|
564
538
|
if (!skills.length) return;
|
|
565
539
|
|
|
@@ -621,11 +595,7 @@ export class Actors extends BasePackCompiler {
|
|
|
621
595
|
}
|
|
622
596
|
|
|
623
597
|
// Being-only combat grouping (mirrors `system.defaultCombatGroup`).
|
|
624
|
-
const defaultCombatGroup = sohlField(
|
|
625
|
-
fm,
|
|
626
|
-
"defaultCombatGroup",
|
|
627
|
-
undefined,
|
|
628
|
-
);
|
|
598
|
+
const defaultCombatGroup = sohlField(fm, "defaultCombatGroup", undefined);
|
|
629
599
|
if (defaultCombatGroup !== undefined) {
|
|
630
600
|
system.defaultCombatGroup = defaultCombatGroup;
|
|
631
601
|
}
|
package/sohl/being-info.mjs
CHANGED
|
@@ -108,8 +108,7 @@ export function deriveBeingInfo(sohl, index) {
|
|
|
108
108
|
const items = Array.isArray(out.items) ? out.items : [];
|
|
109
109
|
if (items.length === 0) return out;
|
|
110
110
|
|
|
111
|
-
const lookup = (type, shortcode) =>
|
|
112
|
-
shortcode ? index.get(`${type}:${shortcode}`) : undefined;
|
|
111
|
+
const lookup = (type, shortcode) => (shortcode ? index.get(`${type}:${shortcode}`) : undefined);
|
|
113
112
|
|
|
114
113
|
/** An item's display name: its own, then the index's, then its shortcode. */
|
|
115
114
|
const displayName = (it, ref, shortcode) =>
|
|
@@ -135,8 +134,7 @@ export function deriveBeingInfo(sohl, index) {
|
|
|
135
134
|
if (!isMap(it)) continue;
|
|
136
135
|
const key = GEAR_TYPE_TO_KEY[it.type];
|
|
137
136
|
if (!key) continue;
|
|
138
|
-
const shortcode =
|
|
139
|
-
typeof it.shortcode === "string" ? it.shortcode : undefined;
|
|
137
|
+
const shortcode = typeof it.shortcode === "string" ? it.shortcode : undefined;
|
|
140
138
|
const ref = lookup(it.type, shortcode);
|
|
141
139
|
const name = displayName(it, ref, shortcode);
|
|
142
140
|
if (!name) continue;
|
|
@@ -153,8 +151,7 @@ export function deriveBeingInfo(sohl, index) {
|
|
|
153
151
|
const talents = [];
|
|
154
152
|
for (const it of items) {
|
|
155
153
|
if (!isMap(it) || it.type !== "mysticalability") continue;
|
|
156
|
-
const shortcode =
|
|
157
|
-
typeof it.shortcode === "string" ? it.shortcode : undefined;
|
|
154
|
+
const shortcode = typeof it.shortcode === "string" ? it.shortcode : undefined;
|
|
158
155
|
const ref = lookup("mysticalability", shortcode);
|
|
159
156
|
// No shortcode fallback here: an ability with neither an inline name
|
|
160
157
|
// nor an index entry has nothing to show, and a row reading like a
|
package/sohl/index.mjs
CHANGED
|
@@ -47,9 +47,4 @@ export * as kbPasses from "./kb-passes.mjs";
|
|
|
47
47
|
// compilers arrived (#1510).
|
|
48
48
|
export { DEFAULT_ITEM_ART, defaultItemArt } from "./default-item-art.mjs";
|
|
49
49
|
export { AFFILIATION_STANDINGS } from "./affiliation-standings.mjs";
|
|
50
|
-
export {
|
|
51
|
-
BEING_TYPE,
|
|
52
|
-
GEAR_TYPE_TO_KEY,
|
|
53
|
-
deriveBeingInfo,
|
|
54
|
-
isBeing,
|
|
55
|
-
} from "./being-info.mjs";
|
|
50
|
+
export { BEING_TYPE, GEAR_TYPE_TO_KEY, deriveBeingInfo, isBeing } from "./being-info.mjs";
|