@heroiclands/package-build 8.1.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +748 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +479 -123
- package/bin/package-build.mjs +27 -69
- package/bin/report.mjs +1 -2
- package/bundle.mjs +2 -10
- package/config.mjs +31 -106
- package/container.mjs +13 -57
- package/content-config.mjs +300 -188
- package/coverage.mjs +14 -55
- package/deploy.mjs +4 -13
- package/docs/content-format.md +1418 -0
- package/e2e.mjs +16 -55
- package/engine/address-charset.mjs +62 -0
- package/engine/address-diff.mjs +1 -4
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +203 -31
- package/engine/code-fences.mjs +4 -13
- package/engine/compendiums.mjs +13 -37
- package/engine/content-address.mjs +6 -10
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +144 -99
- package/engine/content-lint.mjs +12 -16
- package/engine/content-slug.mjs +2 -6
- package/engine/content-tables.mjs +26 -79
- package/engine/diagnostics.mjs +37 -15
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-reference.mjs +6 -20
- package/engine/field-spec.mjs +49 -45
- package/engine/foreign-catalog.mjs +7 -22
- package/engine/foreign-manifests.mjs +1 -4
- package/engine/frontmatter-lint.mjs +347 -43
- package/engine/frontmatter.mjs +3 -8
- package/engine/generate.mjs +36 -20
- package/engine/helpers.mjs +54 -81
- package/engine/homepage.mjs +4 -15
- package/engine/ids.mjs +21 -12
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -9
- package/engine/journals.mjs +4 -14
- package/engine/kb-manifest.mjs +41 -24
- package/engine/macros.mjs +2 -10
- package/engine/manifest-emit.mjs +6 -17
- package/engine/map-notes.mjs +53 -87
- package/engine/note-claims.mjs +383 -0
- package/engine/note-package.mjs +1 -4
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +56 -60
- package/engine/pack-router.mjs +18 -8
- package/engine/prose-config.mjs +20 -4
- package/engine/prose-lint.mjs +61 -17
- package/engine/region-events.mjs +1 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scene-levels.mjs +8 -22
- package/engine/scenes.mjs +31 -47
- package/engine/schema-check.mjs +348 -7
- package/engine/schema-extract.mjs +11 -39
- package/engine/site-build.mjs +13 -38
- package/engine/site-index.mjs +40 -35
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +115 -92
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +147 -183
- package/index.mjs +1 -5
- package/lang.mjs +1 -3
- package/manifest.mjs +10 -37
- package/markdownlint-config.mjs +1 -5
- package/package.json +6 -2
- package/sohl/actors.mjs +251 -68
- package/sohl/being-info.mjs +3 -6
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +4 -6
- package/sohl/item-builders.mjs +1 -3
- package/sohl/item-fields.mjs +16 -34
- package/sohl/items.mjs +111 -17
- package/sohl/kb-passes.mjs +29 -39
- package/sohl/note-schemas.mjs +11 -7
- package/sohl/skill-base.mjs +7 -23
- package/stage.mjs +3 -13
- package/templates.mjs +4 -15
- package/types/bundle.d.mts +1 -1
- package/types/container.d.mts +2 -2
- package/types/content-config.d.mts +48 -4
- package/types/coverage.d.mts +1 -1
- package/types/e2e.d.mts +4 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +15 -2
- package/types/engine/helpers.d.mts +22 -14
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/schema-extract.d.mts +1 -1
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/manifest.d.mts +1 -1
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
package/engine/wikilinks.mjs
CHANGED
|
@@ -16,11 +16,17 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Content notes link to one another with wikilinks rather than file paths:
|
|
18
18
|
*
|
|
19
|
-
* `[[type
|
|
19
|
+
* `[[type-shortcode|Text]]` a document of that type
|
|
20
|
+
* `[[type-shortcode|]]` the same, showing the target's current name
|
|
20
21
|
* `[[Text]]` an alias unique within the source's own type
|
|
21
|
-
* `[[type
|
|
22
|
+
* `[[type-shortcode#slug|T]]` a section (see below)
|
|
22
23
|
* `[[#slug|Text]]` a section of the source note itself
|
|
23
|
-
* `[[doctype
|
|
24
|
+
* `[[doctype-shortcode|T]]` an item's *documentation* (see below)
|
|
25
|
+
*
|
|
26
|
+
* **The pipe decides which of the two namespaces a target belongs to** (#131),
|
|
27
|
+
* and neither falls back to the other — see {@link resolvesAsAddress}, which
|
|
28
|
+
* states the rule for both builds. A piped target is parsed by the address
|
|
29
|
+
* grammar; an unpiped one is looked up in the alias index.
|
|
24
30
|
*
|
|
25
31
|
* The qualifier is the note's **type**, which with its shortcode is the system's
|
|
26
32
|
* logical identity: `(type, shortcode)` is unique by rule (see the Shortcode
|
|
@@ -28,13 +34,13 @@
|
|
|
28
34
|
* unique per type, not per directory, so a directory qualifier would add nothing
|
|
29
35
|
* to the address while breaking every inbound link the moment a note is refiled.
|
|
30
36
|
*
|
|
31
|
-
* The bare form is
|
|
32
|
-
*
|
|
33
|
-
*
|
|
37
|
+
* The bare form is a **name**, not an abbreviated address: it resolves against
|
|
38
|
+
* the aliases of the source's **own type**, so a `doc` reaches any other `doc`
|
|
39
|
+
* by name wherever it is filed. Nothing narrower is consulted — a note's
|
|
34
40
|
* directory and its `category` play no part in resolution. Where two notes of a
|
|
35
41
|
* type legitimately share a name (a rules page and a user-guide page both called
|
|
36
42
|
* "Gear"), the bare form is ambiguous and resolves to neither; the author writes
|
|
37
|
-
* the
|
|
43
|
+
* the `[[type-shortcode|Text]]` address instead.
|
|
38
44
|
*
|
|
39
45
|
* At compile time each becomes a Foundry UUID enricher, routed to the pack that
|
|
40
46
|
* the target's type compiles into (see {@link packForType}):
|
|
@@ -82,18 +88,15 @@
|
|
|
82
88
|
|
|
83
89
|
import crypto from "crypto";
|
|
84
90
|
|
|
85
|
-
import {
|
|
86
|
-
compendiumUuid,
|
|
87
|
-
ITEM_PACK,
|
|
88
|
-
packForType,
|
|
89
|
-
pageUuid,
|
|
90
|
-
PACK_BY_TYPE,
|
|
91
|
-
} from "./ids.mjs";
|
|
91
|
+
import { compendiumUuid, ITEM_PACK, packForType, pageUuid, PACK_BY_TYPE } from "./ids.mjs";
|
|
92
92
|
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
93
93
|
import { replaceOutsideCode } from "./code-fences.mjs";
|
|
94
94
|
// The syntax lives in `./wikilink-syntax.mjs`, so the web resolver and this
|
|
95
95
|
// one cannot disagree about what counts as a link.
|
|
96
|
-
import { authoredLabel, WIKILINK, parseWikilink } from "./wikilink-syntax.mjs";
|
|
96
|
+
import { authoredLabel, WIKILINK, parseWikilink, resolvesAsAddress } from "./wikilink-syntax.mjs";
|
|
97
|
+
// The alias half of the two namespaces: what may be claimed, and how a claim
|
|
98
|
+
// is keyed. Shared with the site build and the link checker (#131).
|
|
99
|
+
import { aliasKey } from "./alias-index.mjs";
|
|
97
100
|
|
|
98
101
|
export { ITEM_PACK, PACK_BY_TYPE, packForType };
|
|
99
102
|
|
|
@@ -194,11 +197,7 @@ export function readQualifier(target, types, packages) {
|
|
|
194
197
|
|
|
195
198
|
const slash = target.lastIndexOf("/");
|
|
196
199
|
if (slash > 0) {
|
|
197
|
-
const read = readTypeAndCode(
|
|
198
|
-
target.slice(0, slash),
|
|
199
|
-
target.slice(slash + 1),
|
|
200
|
-
types,
|
|
201
|
-
);
|
|
200
|
+
const read = readTypeAndCode(target.slice(0, slash), target.slice(slash + 1), types);
|
|
202
201
|
// A slash means qualified whether or not the type is real.
|
|
203
202
|
return read ?? { reason: "unknown-type" };
|
|
204
203
|
}
|
|
@@ -206,11 +205,7 @@ export function readQualifier(target, types, packages) {
|
|
|
206
205
|
const hyphen = target.indexOf("-");
|
|
207
206
|
if (hyphen > 0) {
|
|
208
207
|
// 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
|
-
);
|
|
208
|
+
return readTypeAndCode(target.slice(0, hyphen), target.slice(hyphen + 1), types);
|
|
214
209
|
}
|
|
215
210
|
return null;
|
|
216
211
|
}
|
|
@@ -313,23 +308,14 @@ export function buildWikilinkIndex(docs, packageId, foreign, contentPackage) {
|
|
|
313
308
|
// An item's prose compiles into a separate JournalEntry, addressed
|
|
314
309
|
// by the virtual `doc<type>` qualifier. Its id is derived from the
|
|
315
310
|
// item's, so its address is knowable here too.
|
|
316
|
-
docUuid: compendiumUuid(
|
|
317
|
-
packageId,
|
|
318
|
-
"doc",
|
|
319
|
-
itemDocEntryId(d.id),
|
|
320
|
-
d.docPack,
|
|
321
|
-
),
|
|
311
|
+
docUuid: compendiumUuid(packageId, "doc", itemDocEntryId(d.id), d.docPack),
|
|
322
312
|
});
|
|
323
313
|
|
|
324
|
-
if (d.shortcode)
|
|
325
|
-
byShortcode.set(`${norm(d.type)}/${norm(d.shortcode)}`, d);
|
|
314
|
+
if (d.shortcode) byShortcode.set(`${norm(d.type)}/${norm(d.shortcode)}`, d);
|
|
326
315
|
for (const a of d.aliases ?? []) {
|
|
327
|
-
const key =
|
|
316
|
+
const key = aliasKey(d.type, a);
|
|
328
317
|
// 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
|
-
);
|
|
318
|
+
byAlias.set(key, byAlias.has(key) && byAlias.get(key) !== d ? null : d);
|
|
333
319
|
// Every claimant is kept alongside, because poisoning the alias
|
|
334
320
|
// discards exactly the information needed to report the problem.
|
|
335
321
|
// The note that *cites* an ambiguous alias is innocent — whoever
|
|
@@ -411,11 +397,7 @@ function findForeign(index, read) {
|
|
|
411
397
|
const wanted = norm(read.itemDoc ? `doc${read.type}` : read.type);
|
|
412
398
|
const shortcode = norm(read.shortcode);
|
|
413
399
|
if (read.package) {
|
|
414
|
-
return (
|
|
415
|
-
index.foreign.get(
|
|
416
|
-
`${read.package}-${wanted}-${shortcode}`.toLowerCase(),
|
|
417
|
-
) ?? null
|
|
418
|
-
);
|
|
400
|
+
return index.foreign.get(`${read.package}-${wanted}-${shortcode}`.toLowerCase()) ?? null;
|
|
419
401
|
}
|
|
420
402
|
const hits = [];
|
|
421
403
|
for (const [key, v] of index.foreign) {
|
|
@@ -490,159 +472,141 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
|
|
|
490
472
|
// `offset` is the third replacer argument because the pattern has exactly
|
|
491
473
|
// one capture group. It is what makes two identical unresolved links on
|
|
492
474
|
// 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;
|
|
475
|
+
const out = replaceOutsideCode(markdown, WIKILINK, (all, rawInner, offset) => {
|
|
476
|
+
const parsed = parseWikilink(rawInner);
|
|
477
|
+
const { labelled } = parsed;
|
|
478
|
+
let target = parsed.target;
|
|
479
|
+
// An unlabelled link shows its interior verbatim, anchor included;
|
|
480
|
+
// a labelled one shows its label. An *empty* label is not a label
|
|
481
|
+
// — `[[x|]]` means "show the target's name" — and that reading
|
|
482
|
+
// comes from {@link authoredLabel} so the web resolver cannot draw
|
|
483
|
+
// the line somewhere else (#113).
|
|
484
|
+
let text = labelled ? (authoredLabel(parsed) ?? "") : parsed.inner;
|
|
485
|
+
const slug = parsed.anchor || null;
|
|
507
486
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
487
|
+
// Resolve the document: same-page (empty target), an address, or an
|
|
488
|
+
// alias. **The pipe chooses which**, with no fallback either way
|
|
489
|
+
// (#131) — see {@link resolvesAsAddress}.
|
|
490
|
+
let doc;
|
|
491
|
+
// Set when the qualifier was the virtual `doc<type>` form, so the UUID
|
|
492
|
+
// is built against the item doc entry rather than the item itself.
|
|
493
|
+
let itemDoc = false;
|
|
494
|
+
// Set when the target was read as an address, which is what decides
|
|
495
|
+
// whether a foreign manifest is consulted for it below.
|
|
496
|
+
let addressed = false;
|
|
497
|
+
// Kept for the foreign fallback below, which needs the parsed address.
|
|
498
|
+
let qualifiedRead = null;
|
|
499
|
+
if (target === "" && slug) {
|
|
500
|
+
doc = { type, id, pack, docPack };
|
|
501
|
+
} else if (resolvesAsAddress(parsed)) {
|
|
502
|
+
const qualified = readQualifier(target, index.types, index.packages);
|
|
503
|
+
qualifiedRead = qualified;
|
|
504
|
+
// The author wrote a pipe, so they meant an address. A target that
|
|
505
|
+
// does not parse as one is therefore a defect and not, as it was
|
|
506
|
+
// under the old resolve-by-shape rule, an invitation to try the
|
|
507
|
+
// alias index — which is what let a note *name* resolve here.
|
|
508
|
+
if (!qualified || qualified.reason) {
|
|
509
|
+
unresolved.push({
|
|
510
|
+
link: all,
|
|
522
511
|
target,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
unresolved.push({
|
|
529
|
-
link: all,
|
|
530
|
-
target,
|
|
531
|
-
offset,
|
|
532
|
-
reason: qualified.reason,
|
|
533
|
-
});
|
|
534
|
-
return unresolvedLink(text || target, target);
|
|
535
|
-
}
|
|
536
|
-
if (qualified) {
|
|
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
|
-
}
|
|
512
|
+
offset,
|
|
513
|
+
reason: qualified?.reason ?? "not-an-address",
|
|
514
|
+
addressed: true,
|
|
515
|
+
});
|
|
516
|
+
return unresolvedLink(text || target, target);
|
|
565
517
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
518
|
+
addressed = true;
|
|
519
|
+
itemDoc = qualified.itemDoc;
|
|
520
|
+
doc = index.byShortcode.get(`${qualified.type}/${qualified.shortcode}`);
|
|
521
|
+
} else {
|
|
522
|
+
const key = aliasKey(type, target);
|
|
523
|
+
const hit = index.byAlias.get(key);
|
|
524
|
+
if (hit === null) {
|
|
525
|
+
unresolved.push({
|
|
526
|
+
link: all,
|
|
527
|
+
target,
|
|
528
|
+
offset,
|
|
529
|
+
reason: "ambiguous",
|
|
530
|
+
// Who claimed it, so the report can name the collision
|
|
531
|
+
// rather than the note that merely cites it (#13).
|
|
532
|
+
candidates: (index.aliasClaims?.get(key) ?? []).map((d) => ({
|
|
533
|
+
type: d.type,
|
|
534
|
+
shortcode: d.shortcode,
|
|
535
|
+
name: d.name,
|
|
536
|
+
})),
|
|
537
|
+
});
|
|
538
|
+
return unresolvedLink(text || target, target);
|
|
539
|
+
}
|
|
540
|
+
doc = hit;
|
|
541
|
+
}
|
|
542
|
+
if (!doc) {
|
|
543
|
+
// Nothing local answers. A foreign package may publish this
|
|
544
|
+
// address, in which case the manifest hands back a complete UUID —
|
|
545
|
+
// including, for a section link, the anchor's own — so nothing is
|
|
546
|
+
// derived here.
|
|
547
|
+
const hit = findForeign(index, qualifiedRead);
|
|
548
|
+
if (hit) {
|
|
549
|
+
const uuid = slug ? hit.anchors?.[slug] : hit.uuid;
|
|
550
|
+
if (uuid) {
|
|
551
|
+
return `@UUID[${uuid}]{${text || hit.name || target}}`;
|
|
585
552
|
}
|
|
586
553
|
unresolved.push({
|
|
587
554
|
link: all,
|
|
588
555
|
target,
|
|
589
556
|
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,
|
|
557
|
+
reason: "unknown-anchor",
|
|
558
|
+
addressed: true,
|
|
596
559
|
});
|
|
597
560
|
return unresolvedLink(text || target, target);
|
|
598
561
|
}
|
|
562
|
+
unresolved.push({
|
|
563
|
+
link: all,
|
|
564
|
+
target,
|
|
565
|
+
offset,
|
|
566
|
+
reason: "unknown",
|
|
567
|
+
// An *address* that resolves nowhere is a typo: every package
|
|
568
|
+
// it could name is either built here or vendored, so there is
|
|
569
|
+
// no third possibility left. A bare alias is not — it may
|
|
570
|
+
// simply be prose, or a worldbuilding placeholder.
|
|
571
|
+
addressed,
|
|
572
|
+
});
|
|
573
|
+
return unresolvedLink(text || target, target);
|
|
574
|
+
}
|
|
599
575
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
576
|
+
// An address with no label — `[[skill-clmb|]]` — has no prose to show,
|
|
577
|
+
// a shortcode being an address rather than display text, so the
|
|
578
|
+
// document's **current** name stands in and a rename shows at every
|
|
579
|
+
// citation with no link edited (#1409, #131). A bare `[[Text]]` is
|
|
580
|
+
// already the prose the author wrote, and substituting the canonical
|
|
581
|
+
// name there would rewrite the sentence ("worsens the [[Shock State]]"
|
|
582
|
+
// must not render as "Shock"). The knowledgebase build reads the same
|
|
583
|
+
// authored link the same way.
|
|
584
|
+
if (!text) text = doc.name ?? target;
|
|
607
585
|
|
|
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
|
-
);
|
|
586
|
+
// Both addresses were computed when the target was indexed. An item
|
|
587
|
+
// doc lives in the journals pack under its own derived entry id, and
|
|
588
|
+
// its pages hash against *that* id — not the item's.
|
|
589
|
+
//
|
|
590
|
+
// The one target with no index entry is the note itself: a `[[#slug]]`
|
|
591
|
+
// self-link is resolved from the source's own type and id, which the
|
|
592
|
+
// caller supplied, so it is addressed the same way here.
|
|
593
|
+
const addresses = index.uuidByDoc.get(doc) ?? {
|
|
594
|
+
uuid: compendiumUuid(index.packageId, doc.type, doc.id, doc.pack),
|
|
595
|
+
docUuid: compendiumUuid(index.packageId, "doc", itemDocEntryId(doc.id), doc.docPack),
|
|
596
|
+
};
|
|
597
|
+
const entryUuid = itemDoc ? addresses.docUuid : addresses.uuid;
|
|
598
|
+
const entryId = itemDoc ? itemDocEntryId(doc.id) : doc.id;
|
|
599
|
+
const isJournal = itemDoc || packForType(doc.type).docType === "JournalEntry";
|
|
600
|
+
// A JournalEntry link opens a journal — at its first page, or at the
|
|
601
|
+
// page an anchor names. An Item or Actor link opens that document's
|
|
602
|
+
// *sheet*, which has no sections, so the anchor has nothing to address
|
|
603
|
+
// and is dropped. Forging a JournalEntryPage id onto a document that
|
|
604
|
+
// can never hold one is what made such links dead-end (#1362); an
|
|
605
|
+
// item's pages are addressed through its `doc<type>` counterpart.
|
|
606
|
+
const uuid =
|
|
607
|
+
slug && isJournal ? pageUuid(entryUuid, anchorPageId(entryId, slug)) : entryUuid;
|
|
608
|
+
return `@UUID[${uuid}]{${text}}`;
|
|
609
|
+
});
|
|
646
610
|
|
|
647
611
|
return { markdown: out, unresolved };
|
|
648
612
|
}
|
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": "10.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",
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"container.mjs",
|
|
102
102
|
"content-config.mjs",
|
|
103
103
|
"coverage.mjs",
|
|
104
|
+
"docs",
|
|
104
105
|
"deploy.mjs",
|
|
105
106
|
"e2e.mjs",
|
|
106
107
|
"engine",
|
|
@@ -148,9 +149,12 @@
|
|
|
148
149
|
"build:types": "tsc -p tsconfig.dts.json",
|
|
149
150
|
"format": "prettier --write .",
|
|
150
151
|
"format:check": "prettier --check .",
|
|
151
|
-
"lint": "npm run format:check && npm run lint:markdown",
|
|
152
|
+
"lint": "npm run format:check && npm run lint:markdown && npm run lint:content-format",
|
|
152
153
|
"lint:markdown": "node bin/content-build.mjs markdown",
|
|
153
154
|
"lint:markdown:fix": "node bin/content-build.mjs markdown --fix",
|
|
155
|
+
"lint:content-format": "npm run lint:content-format:schema && npm run lint:content-format:fields",
|
|
156
|
+
"lint:content-format:schema": "node bin/content-build.mjs content-format schema --schema sohl=tests/fixtures/content-format/schema-sohl.json",
|
|
157
|
+
"lint:content-format:fields": "node bin/content-build.mjs content-format fields --fields sohl",
|
|
154
158
|
"changeset": "changeset",
|
|
155
159
|
"changeset:check": "changeset status --since=origin/main",
|
|
156
160
|
"changeset:version": "changeset version && npm install --package-lock-only",
|