@heroiclands/package-build 20.6.0 → 21.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 +181 -0
- package/CONTENT.md +134 -44
- package/bin/content-build.mjs +37 -5
- package/bin/package-build.mjs +77 -0
- package/content-config.mjs +59 -1
- package/docs/api.md +149 -19
- package/docs/commands.md +75 -0
- package/docs/configuration.md +37 -10
- package/docs/content-format.md +450 -49
- package/engine/content-format.mjs +52 -3
- package/engine/content-images.mjs +699 -0
- package/engine/dependency-bump.mjs +218 -0
- package/engine/frontmatter-lint.mjs +89 -2
- package/engine/helpers.mjs +81 -142
- package/engine/index.mjs +15 -0
- package/engine/infobox-registry.mjs +81 -0
- package/engine/infobox-render.mjs +381 -0
- package/engine/infobox.mjs +963 -0
- package/engine/item-registry.mjs +5 -5
- package/engine/journals.mjs +22 -1
- package/engine/map-notes.mjs +11 -5
- package/engine/metadata-index.mjs +5 -0
- package/engine/note-vocabulary.mjs +57 -2
- package/engine/pathnames.mjs +374 -0
- package/engine/pdf-build.mjs +208 -9
- package/engine/pdf-render.mjs +461 -21
- package/engine/pdf-toc.mjs +77 -5
- package/engine/scenes.mjs +2 -1
- package/engine/site-build.mjs +115 -8
- package/engine/site-index.mjs +93 -4
- package/engine/wikilinks.mjs +93 -0
- package/hm3/default-item-art.mjs +14 -15
- package/hm3/index.mjs +3 -0
- package/hm3/infobox.mjs +64 -0
- package/package.json +3 -2
- package/sohl/being-info.mjs +9 -3
- package/sohl/default-item-art.mjs +18 -16
- package/sohl/index.mjs +3 -0
- package/sohl/infobox.mjs +499 -0
- package/types/content-config.d.mts +7 -0
- package/types/engine/content-format.d.mts +36 -0
- package/types/engine/content-images.d.mts +281 -0
- package/types/engine/dependency-bump.d.mts +89 -0
- package/types/engine/frontmatter-lint.d.mts +23 -0
- package/types/engine/helpers.d.mts +30 -72
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +36 -0
- package/types/engine/infobox-render.d.mts +87 -0
- package/types/engine/infobox.d.mts +443 -0
- package/types/engine/item-registry.d.mts +5 -5
- package/types/engine/journals.d.mts +9 -1
- package/types/engine/note-vocabulary.d.mts +51 -0
- package/types/engine/pathnames.d.mts +189 -0
- package/types/engine/pdf-build.d.mts +46 -0
- package/types/engine/pdf-render.d.mts +99 -1
- package/types/engine/pdf-toc.d.mts +10 -5
- package/types/engine/site-build.d.mts +19 -3
- package/types/engine/site-index.d.mts +35 -3
- package/types/engine/wikilinks.d.mts +22 -0
- package/types/hm3/default-item-art.d.mts +5 -6
- package/types/hm3/index.d.mts +1 -0
- package/types/hm3/infobox.d.mts +22 -0
- package/types/sohl/being-info.d.mts +4 -3
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/infobox.d.mts +145 -0
package/content-config.mjs
CHANGED
|
@@ -474,6 +474,10 @@ export function publishesContentPages(config) {
|
|
|
474
474
|
*
|
|
475
475
|
* @typedef {object} RelationshipSpec
|
|
476
476
|
* @property {string} id The other package's id.
|
|
477
|
+
* @property {string} [contentPackage] What the other package's content is
|
|
478
|
+
* called, where that differs from its Foundry
|
|
479
|
+
* id. It is the name a note writes when it
|
|
480
|
+
* addresses a file that package ships.
|
|
477
481
|
* @property {string} [type] `system`, `module`, or `world`.
|
|
478
482
|
* @property {string} [manifest] Where its manifest is published.
|
|
479
483
|
* @property {CompatibilitySpec} [compatibility] The version range of *that*
|
|
@@ -712,6 +716,7 @@ const DOCS_KEYS = ["itemFields"];
|
|
|
712
716
|
const SITE_KEYS = [
|
|
713
717
|
"out",
|
|
714
718
|
"base",
|
|
719
|
+
"assets",
|
|
715
720
|
"packages",
|
|
716
721
|
"sections",
|
|
717
722
|
"readmeSections",
|
|
@@ -728,7 +733,14 @@ const EMPTY_PDF_FONTS = Object.freeze({ serif: "", sans: "", mono: "", path: ""
|
|
|
728
733
|
const SECTION_META_KEYS = ["title", "banner", "description", "listType", "listSubType"];
|
|
729
734
|
const DOC_PAGE_KEYS = ["title", "out", "preamble"];
|
|
730
735
|
const RELATIONSHIP_KINDS = ["systems", "requires", "recommends", "conflicts"];
|
|
731
|
-
const RELATIONSHIP_KEYS = [
|
|
736
|
+
const RELATIONSHIP_KEYS = [
|
|
737
|
+
"id",
|
|
738
|
+
"contentPackage",
|
|
739
|
+
"type",
|
|
740
|
+
"manifest",
|
|
741
|
+
"compatibility",
|
|
742
|
+
"itemCatalog",
|
|
743
|
+
];
|
|
732
744
|
const ITEM_BUILDER_KEYS = ["system", "img", "fields"];
|
|
733
745
|
const ITEM_REGISTRY_KEYS = ["system", "builders"];
|
|
734
746
|
const PACK_KEYS = [
|
|
@@ -1414,6 +1426,38 @@ function normalizeSectionMeta(value, where) {
|
|
|
1414
1426
|
return Object.freeze(out);
|
|
1415
1427
|
}
|
|
1416
1428
|
|
|
1429
|
+
/**
|
|
1430
|
+
* The asset host the website resolves a pathname against.
|
|
1431
|
+
*
|
|
1432
|
+
* The one address in this configuration that is not this repository's own. A
|
|
1433
|
+
* note names a file by the package that owns it and the path inside that
|
|
1434
|
+
* package's `assets/`, and the website serves every package's files from one
|
|
1435
|
+
* host — so the host is the missing half of a web address, and it is written
|
|
1436
|
+
* here because a build has no way to find it out.
|
|
1437
|
+
*
|
|
1438
|
+
* **Absolute, and with no trailing slash.** The forms are joined with a single
|
|
1439
|
+
* `/`, so a trailing one would double it; it is trimmed rather than refused,
|
|
1440
|
+
* because a doubled slash is the sort of thing a reader's eye slides past. A
|
|
1441
|
+
* relative value is refused outright: it would resolve against each page's own
|
|
1442
|
+
* URL, which is the failure the key exists to remove.
|
|
1443
|
+
*
|
|
1444
|
+
* @param {unknown} value - The configured value, or `undefined`.
|
|
1445
|
+
* @returns {string} The host, without its trailing slash; `""` when unset.
|
|
1446
|
+
*/
|
|
1447
|
+
function normalizeSiteAssets(value) {
|
|
1448
|
+
if (value === undefined) return "";
|
|
1449
|
+
const assets = requireNonEmptyString(value, "site.assets");
|
|
1450
|
+
if (!/^https?:\/\/[^/]+/.test(assets)) {
|
|
1451
|
+
fail(
|
|
1452
|
+
"site.assets",
|
|
1453
|
+
"must be an absolute `http://` or `https://` address — it is the host " +
|
|
1454
|
+
"every package's imagery is served from, and a relative value " +
|
|
1455
|
+
"resolves against whichever page happens to carry the image",
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
return assets.replace(/\/+$/, "");
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1417
1461
|
/**
|
|
1418
1462
|
* A map of section name → landing metadata.
|
|
1419
1463
|
*
|
|
@@ -1447,6 +1491,7 @@ function normalizeSite(value) {
|
|
|
1447
1491
|
const empty = Object.freeze({
|
|
1448
1492
|
out: "",
|
|
1449
1493
|
base: "",
|
|
1494
|
+
assets: "",
|
|
1450
1495
|
packages: Object.freeze([]),
|
|
1451
1496
|
sections: Object.freeze({}),
|
|
1452
1497
|
readmeSections: Object.freeze({}),
|
|
@@ -1502,6 +1547,7 @@ function normalizeSite(value) {
|
|
|
1502
1547
|
return Object.freeze({
|
|
1503
1548
|
out: input.out === undefined ? "" : requireNonEmptyString(input.out, "site.out"),
|
|
1504
1549
|
base: input.base === undefined ? "" : requireNonEmptyString(input.base, "site.base"),
|
|
1550
|
+
assets: normalizeSiteAssets(input.assets),
|
|
1505
1551
|
packages: Object.freeze(packages),
|
|
1506
1552
|
sections: normalizeSectionMap(input.sections, "site.sections"),
|
|
1507
1553
|
readmeSections: normalizeSectionMap(input.readmeSections, "site.readmeSections"),
|
|
@@ -1814,6 +1860,18 @@ function normalizeRelationships(value) {
|
|
|
1814
1860
|
const spec = {
|
|
1815
1861
|
id: requireNonEmptyString(rel.id, `${at}.id`),
|
|
1816
1862
|
};
|
|
1863
|
+
// What the other package's *content* is called, where that
|
|
1864
|
+
// differs from its Foundry id. A note addresses a file by the
|
|
1865
|
+
// content package that owns it — `thalorna/assets/…` — and the
|
|
1866
|
+
// Foundry id (`sohl-thalorna`) appears only in the install
|
|
1867
|
+
// path this derives. Omitted where the two are the same word,
|
|
1868
|
+
// which they are for every system.
|
|
1869
|
+
if (rel.contentPackage !== undefined) {
|
|
1870
|
+
spec.contentPackage = requireNonEmptyString(
|
|
1871
|
+
rel.contentPackage,
|
|
1872
|
+
`${at}.contentPackage`,
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1817
1875
|
for (const key of ["type", "manifest"]) {
|
|
1818
1876
|
if (rel[key] !== undefined) {
|
|
1819
1877
|
spec[key] = requireNonEmptyString(rel[key], `${at}.${key}`);
|
package/docs/api.md
CHANGED
|
@@ -338,6 +338,8 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
|
|
|
338
338
|
| `NOTE_VOCABULARY` | `const NOTE_VOCABULARY` | — | looking up, per content type, its `data:` field list and its closed `subType` values |
|
|
339
339
|
| `DECLARED_TAGS` | `const DECLARED_TAGS` | — | looking up the tags a note type may declare, grouped |
|
|
340
340
|
| `declaredTags` | `declaredTags(type, groups)` | `readonly string[]` | reading the declared tags a note of a type may carry, flattened |
|
|
341
|
+
| `applicableTagGroups` | `applicableTagGroups(type, groups)` | `object[]` | reading the declared tag groups that apply to a note type |
|
|
342
|
+
| `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading the single-valued tag slots a note type has, such as a being's kind |
|
|
341
343
|
| `hasTag` | `hasTag(fm, tag)` | `boolean` | checking whether a note carries a given tag, whatever scalar-or-list form it was authored in |
|
|
342
344
|
| `isDraftNote` | `isDraftNote(fm)` | `boolean` | checking whether a note is tagged as an unfinished draft |
|
|
343
345
|
| `subTypeCharsetMessage` | `subTypeCharsetMessage(value)` | `string` | building the message for a `subType` outside the address charset |
|
|
@@ -346,6 +348,66 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
|
|
|
346
348
|
| `dataFields` | `dataFields(type, vocabulary)` | `readonly DataFieldSpec[] \| undefined` | looking up the `data:` keys a note type may carry |
|
|
347
349
|
| `subTypes` | `subTypes(type, vocabulary)` | `readonly string[] \| null \| undefined` | looking up the closed `subType` values a note type declares |
|
|
348
350
|
|
|
351
|
+
### `engine.infobox`
|
|
352
|
+
|
|
353
|
+
The declared infobox: what a note's summary panel holds, decided once and rendered by each medium. The note box's fields are the type's own `data:` vocabulary, in its declared order, so a key added to a type appears everywhere with no second edit; the overlay declares only the label and the handful of keys that carry no row.
|
|
354
|
+
|
|
355
|
+
| Export | Signature | Returns | Use it when |
|
|
356
|
+
| ------------------------- | -------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------- |
|
|
357
|
+
| `INFOBOX_LAYOUTS` | `const INFOBOX_LAYOUTS` | — | looking up the four section layouts and the property each carries its content in |
|
|
358
|
+
| `INFOBOX_VALUE_KINDS` | `const INFOBOX_VALUE_KINDS` | — | enumerating what a row's value may be |
|
|
359
|
+
| `DURATION_LABELS` | `const DURATION_LABELS` | — | naming a duration pair — the roll and the flat number of seconds — in either overlay |
|
|
360
|
+
| `GEAR_UNITS` | `const GEAR_UNITS` | — | naming a gear item's price and weight, and the unit each carries, in either overlay |
|
|
361
|
+
| `UNSET_VALUES` | `const UNSET_VALUES` | — | enumerating the words a corpus writes when it means "there is nothing here" |
|
|
362
|
+
| `applyUnit` | `applyUnit(kind, value, unit)` | `object` | putting a declared unit on a row's value, where the quantity is |
|
|
363
|
+
| `NOT_AVAILABLE` | `const NOT_AVAILABLE` | — | naming what a mapped system that produced no document says |
|
|
364
|
+
| `NOTHING_BEYOND_PROFILE` | `const NOTHING_BEYOND_PROFILE` | — | naming what a system holding nothing the note box has not shown says |
|
|
365
|
+
| `NOTE_BOX_ID` | `const NOTE_BOX_ID` | — | naming the note infobox, which is not a system id |
|
|
366
|
+
| `NOTE_BOX_TITLE` | `const NOTE_BOX_TITLE` | — | naming the note infobox's heading |
|
|
367
|
+
| `NOTE_SECTION_ID` | `const NOTE_SECTION_ID` | — | naming the note infobox's single section |
|
|
368
|
+
| `NOTE_FIELD_PRESENTATION` | `const NOTE_FIELD_PRESENTATION` | — | looking up a `data:` key's label, the group it composes into, or why it carries no row |
|
|
369
|
+
| `assertInfoboxSet` | `assertInfoboxSet(boxes, fm, options)` | `readonly object[]`, throws | refusing a page that carries anything but the boxes its type maps to |
|
|
370
|
+
| `buildInfoboxes` | `buildInfoboxes(fm, options)` | `object[]` | building every box a note carries, against a given set of maps and declarations |
|
|
371
|
+
| `defineInfobox` | `defineInfobox(declaration)` | `object` | declaring one system's half of the infobox |
|
|
372
|
+
| `hasRenderableValue` | `hasRenderableValue(kind, value)` | `boolean` | deciding whether a built value is worth a row |
|
|
373
|
+
| `hasValue` | `hasValue(value)` | `boolean` | deciding whether an authored value is worth a row |
|
|
374
|
+
| `humanizeFieldName` | `humanizeFieldName(name)` | `string` | turning a declared key into the label a reader sees |
|
|
375
|
+
| `humanizeValue` | `humanizeValue(value)` | `string` | turning an authored value into readable text |
|
|
376
|
+
| `isDeclaredDefault` | `isDeclaredDefault(field, raw)` | `boolean` | deciding whether a value is the one the field's own declaration would have supplied |
|
|
377
|
+
| `isUnsetSentinel` | `isUnsetSentinel(value)` | `boolean` | deciding whether a value is a word meaning "nothing here" rather than a value |
|
|
378
|
+
| `linkValue` | `linkValue(ref, resolve, hint)` | `object` | resolving one reference into a `link` value |
|
|
379
|
+
| `noteInfobox` | `noteInfobox(fm, options)` | `object` | building the note box alone, from the type's `data:` vocabulary |
|
|
380
|
+
| `overlayFor` | `overlayFor(presentation, type, name)` | `object` | reading a field's overlay entry, preferring the `<type>.<field>` key over the bare one |
|
|
381
|
+
| `presentValue` | `presentValue(value)` | `string` | showing a value in a row, capitalising an enumerated one and leaving prose as written |
|
|
382
|
+
| `requiredInfoboxIds` | `requiredInfoboxIds(fm, options)` | `string[]` | asking which boxes a note's type maps to |
|
|
383
|
+
| `sectionHolds` | `sectionHolds(section)` | `boolean` | deciding whether a section holds anything a medium would draw |
|
|
384
|
+
| `systemRowsSection` | `systemRowsSection(fm, fields, ctx)` | `object[]` | building the rows a type's own field declaration yields |
|
|
385
|
+
| `valueKindOf` | `valueKindOf(field, value)` | `string` | reading the value kind a field declaration implies |
|
|
386
|
+
|
|
387
|
+
### `engine.infoboxRegistry`
|
|
388
|
+
|
|
389
|
+
The infobox declarations this toolchain ships, one per system, and the single call each medium makes to build a note's boxes.
|
|
390
|
+
|
|
391
|
+
| Export | Signature | Returns | Use it when |
|
|
392
|
+
| ----------------- | ---------------------------- | --------------------- | ------------------------------------------------------------------------------ |
|
|
393
|
+
| `KNOWN_INFOBOXES` | `const KNOWN_INFOBOXES` | — | enumerating every system's infobox declaration, in the order a page shows them |
|
|
394
|
+
| `infoboxFor` | `infoboxFor(system)` | `object \| undefined` | looking up one system's declaration by its id |
|
|
395
|
+
| `noteInfoboxes` | `noteInfoboxes(fm, options)` | `object[]`, throws | building every box one note carries, wired to the shipped registries |
|
|
396
|
+
|
|
397
|
+
### `engine.infoboxRender`
|
|
398
|
+
|
|
399
|
+
Drawing a declared infobox: HTML for a Foundry Journal Page, Typst for the book. The website's boxes travel in front matter and the site theme draws them, so there is no renderer for it here.
|
|
400
|
+
|
|
401
|
+
| Export | Signature | Returns | Use it when |
|
|
402
|
+
| ---------------------- | ------------------------------------- | --------- | ------------------------------------------------------------------------ |
|
|
403
|
+
| `infoboxTypstPreamble` | `infoboxTypstPreamble()` | `string` | emitting the panel definitions the book's bodies call, once per document |
|
|
404
|
+
| `infoboxesToHtml` | `infoboxesToHtml(boxes, options)` | `string` | drawing the boxes as `<details>` disclosures, open by default |
|
|
405
|
+
| `infoboxesToTypst` | `infoboxesToTypst(boxes, options)` | `string` | drawing the boxes as panels that break between their sections |
|
|
406
|
+
| `linkToHtml` | `linkToHtml(value)` | `string` | drawing a link value as an anchor on its URL |
|
|
407
|
+
| `linkToTypst` | `linkToTypst(value, links, labelFor)` | `string` | drawing a link value as a cross-reference into the book |
|
|
408
|
+
| `linkToUuid` | `linkToUuid(value)` | `string` | drawing a link value as a Foundry document reference |
|
|
409
|
+
| `sectionHasContent` | `sectionHasContent(section)` | `boolean` | deciding whether a section holds anything worth drawing |
|
|
410
|
+
|
|
349
411
|
### `engine.systems`
|
|
350
412
|
|
|
351
413
|
The closed registry of system ids, and the `none` that stands for no system at all. An unknown system value is an error; adding a system is a data change to this registry rather than a hardcoded set scattered through the pipeline.
|
|
@@ -563,6 +625,41 @@ Raw HTML in a note's prose, reported. **A note is markdown.** What markdown cann
|
|
|
563
625
|
| `checkHtml` | `function checkHtml(body, file,` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} One finding per tag, in source order. | Every raw HTML tag in one note's body. |
|
|
564
626
|
| `lintContentHtml` | `function lintContentHtml(contentBase,` | {{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}} The findings, and how many files were read. | Walk a content tree and report raw HTML in every note's prose. |
|
|
565
627
|
|
|
628
|
+
### `engine.contentImages`
|
|
629
|
+
|
|
630
|
+
An image saying how wide it is and where it sits. A markdown image carries no indication of either, so each of the three surfaces decides for itself and the author — who is the one who knows — has no way to say. A directive in the curly-attribute convention Pandoc and Kramdown use closes that, in two closed vocabularies: a width class, and a `float:` position.
|
|
631
|
+
|
|
632
|
+
| Export | Signature | Returns | Use it when |
|
|
633
|
+
| --------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
634
|
+
| `IMAGE_CLASSES` | `const IMAGE_CLASSES` | — | The width classes an image may carry, and what each means to a renderer. |
|
|
635
|
+
| `IMAGE_FLOATS` | `const IMAGE_FLOATS` | — | The `float:` positions an image may take, and where each puts it. |
|
|
636
|
+
| `IMAGE_FIGURE_CLASS` | `const IMAGE_FIGURE_CLASS` | — | The class every figure carries, whatever its width or position. |
|
|
637
|
+
| `IMAGE_PATTERN` | `const IMAGE_PATTERN` | — | A markdown image, with the directive it may carry. |
|
|
638
|
+
| `imageSourceProblem` | `function imageSourceProblem(src)` | {string} The problem, as a finding's sentence, or `""`. | What is wrong with an image's address, or `""` when nothing is. |
|
|
639
|
+
| `parseImageDirective` | `function parseImageDirective(raw)` | {{classes: string[], float: string, problems: string[]}} What was written, and what cannot be honoured. | Read the directive on an image. |
|
|
640
|
+
| `figureClasses` | `function figureClasses(directive)` | {string} A space-separated class list. | The classes a figure carries, from a parsed directive. |
|
|
641
|
+
| `escapeHtml` | `function escapeHtml(text)` | {string} The same value, safe in markup. | Text going inside an HTML attribute or between tags. |
|
|
642
|
+
| `imageFigureHtml` | `function imageFigureHtml(image)` | {string} The figure, as one HTML block. | One image as the `<figure>` both HTML surfaces render. |
|
|
643
|
+
| `standsAlone` | `function standsAlone(text, start, end)` | {boolean} Whether the match is a block of its own. | Whether a match sits alone in its own paragraph. |
|
|
644
|
+
| `imagesIn` | `function imagesIn(body)` | {Array<{alt: string, src: string, title: string, directive: string, index: number, length: number, block: boolean}>} One entry per image. | Every image in one body, with its directive and its position. |
|
|
645
|
+
| `imageSourcesIn` | `function imageSourcesIn(body)` | {string[]} The addresses, with repeats. | Every image address one body names, in order of appearance. |
|
|
646
|
+
| `checkImages` | `function checkImages(body, file,` | {Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>} One finding per defect, in source order. | Every defect in one note's images. |
|
|
647
|
+
| `lintContentImages` | `function lintContentImages(contentBase,` | {{findings: Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>, files: number}} What it found. | Walk a content tree and report every image it cannot render as authored. |
|
|
648
|
+
| `renderImageFigures` | `function renderImageFigures(body, resolveSrc)` | {string} The same body, with each block image as a `<figure>`. | Rewrite every block image in a body into the figure the website publishes. |
|
|
649
|
+
| `imagePlugin` | `function imagePlugin()` | {(md: object) => void} A markdown-it plugin. | A markdown-it plugin that reads an image's directive and renders its figure. |
|
|
650
|
+
|
|
651
|
+
### `engine.pathnames`
|
|
652
|
+
|
|
653
|
+
One authored pathname, and the four addresses it resolves to. A note names a file once — in `img:`, in `data.portrait:`, in the body of a markdown image — and the first segment says which package owns it when an `assets/` follows. Every surface derives its own address from that one statement: the path inside a Foundry install, the file in the owning repository's tree, the address the website serves, and where the book stages its copy.
|
|
654
|
+
|
|
655
|
+
| Export | Signature | Returns | Use it when |
|
|
656
|
+
| ------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
657
|
+
| `ASSETS_SEGMENT` | `const ASSETS_SEGMENT` | — | The directory a package ships its files in. |
|
|
658
|
+
| `PATHNAME_SURFACES` | `const PATHNAME_SURFACES` | — | The surfaces one authored pathname resolves for. |
|
|
659
|
+
| `pathnameProblem` | `function pathnameProblem(raw)` | {string} The problem, as a finding's sentence, or `""`. | What is wrong with an authored pathname, or `""` when nothing is. |
|
|
660
|
+
| `packageAddresses` | `function packageAddresses(config)` | {Map<string, {root: string\|null, id: string\|null, own: boolean}>} The packages, by package name. | Every content package this build can resolve a pathname against. |
|
|
661
|
+
| `resolvePathname` | `function resolvePathname(raw, config)` | {PathnameForms\|null} The four forms, or `null` when the note names no file. | Resolve one authored pathname into the address each surface serves. |
|
|
662
|
+
|
|
566
663
|
### `engine.contentLinks`
|
|
567
664
|
|
|
568
665
|
Resolving every link in a content tree, and reporting the ones that land nowhere. Three link defects survive both content builds silently, so neither the pack compilers nor a site build catches them:
|
|
@@ -672,6 +769,7 @@ Wikilink resolution for the pack compilers. Content notes link to one another wi
|
|
|
672
769
|
| `readQualifier` | `function readQualifier(target, types, packages)` | {{type: string, shortcode: string, itemDoc: boolean, package?: string, system?: string, reason?: undefined} \| {reason: "unknown-type"} \| null} The resolved qualifier; a `reason` when the target is definitely qualified but names no known type; or `null` when it is not an address at all. | Read a link target as a **qualified** `type-shortcode` reference, or report that it does not parse as one. |
|
|
673
770
|
| `anchorPageId` | `function anchorPageId(noteId, anchorSlug)` | {string} A 16-character alphanumeric id. | The deterministic JournalEntryPage id for one anchor: SHA-256 of `"<noteId>-<anchorSlug>"`, base64-encoded, reduced to the 16 alphanumeric characters a Foundry id allows. |
|
|
674
771
|
| `buildWikilinkIndex` | `function buildWikilinkIndex(docs, packageId, foreign, contentPackage)` | {{byShortcode: Map<string, object>, types: Set<string>}} `types` is every type the tree actually contains, so a qualifier naming no real type can be told apart from a missing target. | Builds the link-resolution tables for a content tree. |
|
|
772
|
+
| `resolveReference` | `function resolveReference(index, ref, hint)` | {{name?: string, uuid?: string, address?: string, subType?: string}\|undefined} The target, or `undefined` where nothing answers. | Resolve one reference — a bare shortcode, a short address or a canonical one — to what a compendium can use. |
|
|
675
773
|
| `convertWikilinks` | `function convertWikilinks(markdown,` | {{markdown: string, unresolved: Array<{link: string, target: string, offset: number, reason: string, packages?: string[], anchor?: string}>}} Each `reason` is one of {@link LINK_FINDING_REASONS}, the vocabulary all three resolvers share — `ambiguous` carries the claiming `packages` and `unknown-anchor` the section it named. | Rewrites every wikilink in a markdown body as a Foundry UUID enricher. |
|
|
676
774
|
|
|
677
775
|
### `engine.wikilinkSyntax`
|
|
@@ -694,10 +792,11 @@ What a `[[…]]` **is**, before anything decides where it points. One authored l
|
|
|
694
792
|
|
|
695
793
|
**The address index a site build resolves its wikilinks against.** Every consumer that publishes a content tree as a website has to answer the same question — given `[[Something]]`, which page? — and every one of them answered it with its own copy of the same 150 lines. `sohl`'s and `sohl-thalorna`'s site builds still share 147 identical lines of it, comments and indentation aside. This is that shared half, lifted out whole.
|
|
696
794
|
|
|
697
|
-
| Export
|
|
698
|
-
|
|
|
699
|
-
| `buildSiteIndex`
|
|
700
|
-
| `wikiContext`
|
|
795
|
+
| Export | Signature | Returns | Use it when |
|
|
796
|
+
| ------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
797
|
+
| `buildSiteIndex` | `function buildSiteIndex(entries,` | {SiteIndex} The index, and what could not be addressed unambiguously. | Build the address index a site's wikilink resolver reads. |
|
|
798
|
+
| `wikiContext` | `function wikiContext(built,` | {object} The resolver context. | The per-page context a wikilink resolver takes. |
|
|
799
|
+
| `resolveInfoboxRef` | `function resolveInfoboxRef(siteIndex, ref, hint)` | {{name?: string, url?: string, address?: string, subType?: string}\|undefined} The page, or `undefined` where nothing answers. | Resolve one infobox reference — a bare shortcode, a short address or a canonical one — against a site index. |
|
|
701
800
|
|
|
702
801
|
### `engine.pdfToc`
|
|
703
802
|
|
|
@@ -714,16 +813,17 @@ The document tree a PDF is built from, and the plan it resolves to (#316). The p
|
|
|
714
813
|
|
|
715
814
|
A note's markdown, and a document plan, rendered as Typst source. **This module emits text and reads nothing.** It takes markdown and a plan and returns a `.typ` document; the filesystem, the note bodies and the compiler that turns the result into a PDF all live in {@link module:engine/pdf-build}. That split is what lets the outline, the table of contents, every anchor and every link destination be asserted in a unit test with no renderer installed — which is most of what a book has to get right, and all of what a test can check without eyes.
|
|
716
815
|
|
|
717
|
-
| Export | Signature
|
|
718
|
-
| ----------------------- |
|
|
719
|
-
| `escapeTypst` | `function escapeTypst(text)`
|
|
720
|
-
| `escapeTypstString` | `function escapeTypstString(text)`
|
|
721
|
-
| `labelFor` | `function labelFor(anchor)`
|
|
722
|
-
| `createParser` | `function createParser(registry)`
|
|
723
|
-
| `markdownToTypst` | `function markdownToTypst(markdown, opts`
|
|
724
|
-
| `renderBook` | `renderBook({ plan, bodies, title, subtitle, front, fonts, version })` | {string} A complete `.typ` document. | The whole book, as one Typst document. |
|
|
725
|
-
| `resolveDanglingLabels` | `function resolveDanglingLabels(source, findings`
|
|
726
|
-
| `iconNamesIn` | `function iconNamesIn(markdown)`
|
|
816
|
+
| Export | Signature | Returns | Use it when |
|
|
817
|
+
| ----------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
818
|
+
| `escapeTypst` | `function escapeTypst(text)` | {string} The same text, inert. | Escape literal text for Typst markup. |
|
|
819
|
+
| `escapeTypstString` | `function escapeTypstString(text)` | {string} The same value, quotable. | Escape a string going inside Typst string quotes, as a `#link` URL does. |
|
|
820
|
+
| `labelFor` | `function labelFor(anchor)` | {string} A Typst label name. | A Typst label, from a plan anchor. |
|
|
821
|
+
| `createParser` | `function createParser(registry)` | {object} A markdown-it instance. | A markdown-it configured to parse, not to render. |
|
|
822
|
+
| `markdownToTypst` | `function markdownToTypst(markdown, opts` | {string} Typst markup. | Render markdown as Typst content. |
|
|
823
|
+
| `renderBook` | `renderBook({ plan, bodies, title, subtitle, front, fonts, version, preamble, banners })` | {string} A complete `.typ` document. | The whole book, as one Typst document. |
|
|
824
|
+
| `resolveDanglingLabels` | `function resolveDanglingLabels(source, findings` | {string} The same document, with no reference left dangling. | Point every internal link at a label the document actually declares. |
|
|
825
|
+
| `iconNamesIn` | `function iconNamesIn(markdown)` | {string[]} The names, in order of appearance, with repeats. | Every icon name a body uses, so a build can resolve them once. |
|
|
826
|
+
| `bookTypstPreamble` | `function bookTypstPreamble()` | {string} Typst markup. | The Typst definitions the book's page furniture is drawn with. |
|
|
727
827
|
|
|
728
828
|
### `engine.pdfFonts`
|
|
729
829
|
|
|
@@ -739,11 +839,13 @@ Which glyph an icon name resolves to, read from the font that carries it. {@link
|
|
|
739
839
|
|
|
740
840
|
The content tree, built into a book. The I/O half of the PDF surface: it reads the configuration, the document tree and the notes, drives the passes the site build already owns, hands the result to {@link module:engine/pdf-render} and runs Typst over what comes back. Everything about _what the book says_ is decided in the pure half; this module is where the filesystem and the compiler live.
|
|
741
841
|
|
|
742
|
-
| Export
|
|
743
|
-
|
|
|
744
|
-
| `pdfFileName`
|
|
745
|
-
| `buildPdf`
|
|
746
|
-
| `
|
|
842
|
+
| Export | Signature | Returns | Use it when |
|
|
843
|
+
| ----------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
844
|
+
| `pdfFileName` | `function pdfFileName(artifact, version)` | {string} `<artifact>-<version>.pdf`, or `<artifact>.pdf` unversioned. | The file name a downloaded book identifies itself by. |
|
|
845
|
+
| `buildPdf` | `async buildPdf({ config, out, version, compile })` | {Promise<object>} `{ built, reason, findings, typ, pdf, stats }`. | Build the book. |
|
|
846
|
+
| `stagedImagePath` | `function stagedImagePath(src, config)` | {{from: string, to: string}\|null} The file, and where under the output directory it is staged. | The file on disk an authored image address names, or `null`. |
|
|
847
|
+
| `stageBanners` | `function stageBanners(entries, config, outDir, findings)` | {Map<string, string>} Declared path → the staged file's path, relative to the `.typ`. | Copy every banner the document tree names into the output directory. |
|
|
848
|
+
| `compileTypst` | `function compileTypst(typPath, pdfPath, pdf` | {{ok: boolean, message: string}} What happened. | Run Typst over the emitted source. |
|
|
747
849
|
|
|
748
850
|
### `engine.baseCompiler`
|
|
749
851
|
|
|
@@ -940,6 +1042,24 @@ SoHL's Actor pass — what a SoHL `being` document holds and nothing else: the b
|
|
|
940
1042
|
| -------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
941
1043
|
| `Actors` | `class Actors` | — | not called directly — imported and driven by `engine/generate.mjs`; must run after the items passes, since it reads their generated JSON trees |
|
|
942
1044
|
|
|
1045
|
+
### `sohl.infobox`
|
|
1046
|
+
|
|
1047
|
+
Which of SoHL's facts a note's summary panel carries, and how they group. Read off the same field declaration the compiler obeys, with a builder of its own only where a box is derived rather than read field by field.
|
|
1048
|
+
|
|
1049
|
+
| Export | Signature | Returns | Use it when |
|
|
1050
|
+
| ------------------------- | ------------------------------- | --------------------- | ----------------------------------------------------------------------------------- |
|
|
1051
|
+
| `SOHL_INFOBOX` | `const SOHL_INFOBOX` | — | reading SoHL's infobox declaration |
|
|
1052
|
+
| `SOHL_INFOBOX_TITLE` | `const SOHL_INFOBOX_TITLE` | — | naming SoHL's box |
|
|
1053
|
+
| `SOHL_FIELD_PRESENTATION` | `const SOHL_FIELD_PRESENTATION` | — | looking up what one of SoHL's fields is called, or why it carries no row |
|
|
1054
|
+
| `PROTECTION_FIELDS` | `const PROTECTION_FIELDS` | — | reading the declarations of the aspects armour is rated against, in the order shown |
|
|
1055
|
+
| `UNSTATED` | `const UNSTATED` | — | naming what a strike mode shows where a value was not stated |
|
|
1056
|
+
| `armorSections` | `armorSections(fm, ctx)` | `object[]` | building armour's box, protection included |
|
|
1057
|
+
| `beingSections` | `beingSections(fm, ctx)` | `object[]` | building a being's attributes, skills, mystical abilities and equipment |
|
|
1058
|
+
| `decodeItem` | `decodeItem(entry)` | `object \| undefined` | reading what one `sohl.items` entry names, whichever form it was written in |
|
|
1059
|
+
| `projectileSections` | `projectileSections(fm, ctx)` | `object[]` | building a projectile's box, its impact composed into one row |
|
|
1060
|
+
| `strikeModes` | `strikeModes(declared)` | `[string, object][]` | reading a weapon's strike modes, whichever of the two shapes were authored |
|
|
1061
|
+
| `weaponSections` | `weaponSections(fm, ctx)` | `object[]` | building a weapon's box, strike modes included |
|
|
1062
|
+
|
|
943
1063
|
### `sohl.kbPasses`
|
|
944
1064
|
|
|
945
1065
|
The `sohl` knowledgebase's own body passes: two rewrites driven by a TypeDoc symbol map and a repository layout only this package has, named from `site.passOptions` the same way an asset transform is named from configuration. Neither rewrite ever fails a build — an unresolved `{@link}` degrades to a code span, and a relative link outside the documentation tree becomes a GitHub blob URL — but building the bundle from a misconfigured `symbolMap` fails loudly before any page renders.
|
|
@@ -1002,6 +1122,16 @@ HM3's note-type → document-subtype map. Unlike SoHL's near-identity map, HM3's
|
|
|
1002
1122
|
| `HM3_TYPE_KEY` | `const HM3_TYPE_KEY` | — | reading the frontmatter key (`hm3.type`) inside the `hm3:` block that resolves a one-to-many content type to its HM3 document subtype |
|
|
1003
1123
|
| `HM3_DOCUMENT_SUBTYPES` | `const HM3_DOCUMENT_SUBTYPES` | — | looking up which Foundry document and subtype a content type compiles into under HM3; a type absent from this map compiles into no HM3 document at all |
|
|
1004
1124
|
|
|
1125
|
+
### `hm3.infobox`
|
|
1126
|
+
|
|
1127
|
+
Which of HM3's facts a note's summary panel carries, read off the same field list the item builders obey.
|
|
1128
|
+
|
|
1129
|
+
| Export | Signature | Returns | Use it when |
|
|
1130
|
+
| ------------------------ | ------------------------------ | ------- | --------------------------------------------- |
|
|
1131
|
+
| `HM3_INFOBOX` | `const HM3_INFOBOX` | — | reading HM3's infobox declaration |
|
|
1132
|
+
| `HM3_INFOBOX_TITLE` | `const HM3_INFOBOX_TITLE` | — | naming HM3's box |
|
|
1133
|
+
| `HM3_FIELD_PRESENTATION` | `const HM3_FIELD_PRESENTATION` | — | looking up what one of HM3's fields is called |
|
|
1134
|
+
|
|
1005
1135
|
### `hm3.items`
|
|
1006
1136
|
|
|
1007
1137
|
HM3's Item pass — the parts of compiling a note into an HM3 Item that are facts about HM3: the `description` key rendered from a note's `{#appearance}` section (the one HM3 Item field with nowhere else to put it), and the `flags.hm3.templatePriority` flag HM3's data model has no field for. There is no HM3 equivalent of SoHL's `docHtml` — the prose still compiles into its JournalEntry, it just isn't addressed from the item.
|
package/docs/commands.md
CHANGED
|
@@ -437,6 +437,81 @@ package-build: registry and §3 agree (11 labels).
|
|
|
437
437
|
|
|
438
438
|
None.
|
|
439
439
|
|
|
440
|
+
### `package-build bump [packages..]`
|
|
441
|
+
|
|
442
|
+
**NAME**
|
|
443
|
+
|
|
444
|
+
Take a newer version of a dependency, keeping the lockfile's formatting.
|
|
445
|
+
|
|
446
|
+
**SYNOPSIS**
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
package-build bump [packages..] [--tag <tag>] [--check]
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**DESCRIPTION**
|
|
453
|
+
|
|
454
|
+
Takes the newest published version of one or more declared dependencies,
|
|
455
|
+
updating `package-lock.json` — and `package.json` when the declared range has to
|
|
456
|
+
move — then restoring both files to the indentation they already used.
|
|
457
|
+
|
|
458
|
+
npm performs the resolution, so a version whose dependency set differs from the
|
|
459
|
+
one it replaces is handled as correctly as one that moves three lines. Editing
|
|
460
|
+
those three lines by hand is only right while the two dependency sets are
|
|
461
|
+
identical, and nothing tells the author when they are not.
|
|
462
|
+
|
|
463
|
+
The indentation is the reason this exists rather than `npm install` being run
|
|
464
|
+
directly. Every repository consuming this toolchain writes `package-lock.json`
|
|
465
|
+
with four spaces and prettier-ignores it; npm rewrites it with two, turning a
|
|
466
|
+
three-line version change into a whole-file reformat no reviewer can read past.
|
|
467
|
+
|
|
468
|
+
Named no packages, it takes every `@heroiclands/*` dependency the manifest
|
|
469
|
+
declares. That scope is the one a person bumps by hand — a first-party release
|
|
470
|
+
is taken the moment it publishes, usually to unblock the change that prompted
|
|
471
|
+
it, while third-party updates arrive from Dependabot on their own schedule.
|
|
472
|
+
|
|
473
|
+
The version reported is the one the **lockfile** resolves, not the range the
|
|
474
|
+
manifest declares, because `npm ci` installs from the lockfile. Above 1.0 a
|
|
475
|
+
caret range usually already admits the new version, so the manifest does not
|
|
476
|
+
move and the lockfile is the whole change; below 1.0 a caret is locked to the
|
|
477
|
+
minor, so it does.
|
|
478
|
+
|
|
479
|
+
Writes no `node_modules`: install the result with `npm ci`.
|
|
480
|
+
|
|
481
|
+
**OPTIONS**
|
|
482
|
+
|
|
483
|
+
| Option | Type | Default | Description |
|
|
484
|
+
| --------- | ------- | -------- | ------------------------------------------- |
|
|
485
|
+
| `--tag` | string | `latest` | The dist-tag to take. |
|
|
486
|
+
| `--check` | boolean | `false` | Report what would change and write nothing. |
|
|
487
|
+
|
|
488
|
+
**EXIT STATUS**
|
|
489
|
+
|
|
490
|
+
1 if the working directory holds no `package.json` or no `package-lock.json`. 1
|
|
491
|
+
if a named package is not a declared dependency. Otherwise 0, including when
|
|
492
|
+
every package is already current.
|
|
493
|
+
|
|
494
|
+
**EXAMPLES**
|
|
495
|
+
|
|
496
|
+
```
|
|
497
|
+
$ package-build bump
|
|
498
|
+
@heroiclands/package-build 20.6.0 → 20.7.0
|
|
499
|
+
kept the existing indentation of package-lock.json
|
|
500
|
+
|
|
501
|
+
Install it with `npm ci`, which resolves from the lockfile this just moved.
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
$ package-build bump @heroiclands/hugo-theme --check
|
|
506
|
+
@heroiclands/hugo-theme 0.5.0 → 0.6.0
|
|
507
|
+
|
|
508
|
+
Run without --check to take it.
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**SEE ALSO**
|
|
512
|
+
|
|
513
|
+
`package-build clean`.
|
|
514
|
+
|
|
440
515
|
### `package-build yaml [paths..]`
|
|
441
516
|
|
|
442
517
|
**NAME**
|
package/docs/configuration.md
CHANGED
|
@@ -689,6 +689,7 @@ Any other key under `docs.itemFields` is refused:
|
|
|
689
689
|
| ----------------------- | -------- | ------- |
|
|
690
690
|
| `site.out` | string | `""` |
|
|
691
691
|
| `site.base` | string | `""` |
|
|
692
|
+
| `site.assets` | string | `""` |
|
|
692
693
|
| `site.packages` | string[] | `[]` |
|
|
693
694
|
| `site.sections` | object | `{}` |
|
|
694
695
|
| `site.readmeSections` | object | `{}` |
|
|
@@ -706,7 +707,7 @@ rewrites.
|
|
|
706
707
|
|
|
707
708
|
> ``package-build config: `site` must be a mapping.``
|
|
708
709
|
|
|
709
|
-
> ``package-build config: `site.<key>` is not a recognized option (expected one of: out, base, packages, sections, readmeSections, landing, trees, pass, passOptions, backfillSections).``
|
|
710
|
+
> ``package-build config: `site.<key>` is not a recognized option (expected one of: out, base, assets, packages, sections, readmeSections, landing, trees, pass, passOptions, backfillSections).``
|
|
710
711
|
|
|
711
712
|
`site.out` is the output root, resolved by `engine/site-build.mjs`; unset,
|
|
712
713
|
it is refused **at build time** rather than by `defineConfig` (an unset
|
|
@@ -715,6 +716,23 @@ wipes on every run would be the working tree):
|
|
|
715
716
|
|
|
716
717
|
> `site.out is not set, so there is nowhere to write the site. Refusing to continue: the output directory is wiped on every run, and an unset one resolves to the repository root.`
|
|
717
718
|
|
|
719
|
+
`site.assets` is the host every package's imagery is served from, and it is
|
|
720
|
+
the one address in this file that is not this repository's own. A note names
|
|
721
|
+
a file by the package that owns it and the path inside that package's
|
|
722
|
+
`assets/` (see `docs/content-format.md`), and the website joins the two onto
|
|
723
|
+
this host — `https://cdn.heroiclands.org` + `/thalorna` +
|
|
724
|
+
`/images/map.webp`. A build has no way to find the host out, so a page
|
|
725
|
+
carrying a package-owned image with none set is an error naming this key.
|
|
726
|
+
Absolute, and the trailing slash is trimmed:
|
|
727
|
+
|
|
728
|
+
> ``package-build config: `site.assets` must be an absolute `http://` or `https://` address — it is the host every package's imagery is served from, and a relative value resolves against whichever page happens to carry the image.``
|
|
729
|
+
|
|
730
|
+
The consuming Hugo site spells the same host as `params.cdnBaseURL`, which
|
|
731
|
+
its theme resolves a relative asset path against. The two are the same
|
|
732
|
+
address written for two readers: this one is what the toolchain emits into a
|
|
733
|
+
page, that one is what the theme joins onto anything the toolchain left
|
|
734
|
+
relative.
|
|
735
|
+
|
|
718
736
|
`site.packages` names which content packages' notes the site walks, beyond
|
|
719
737
|
this one's own; `site.pass` names a repository's own body-rewrite bundle
|
|
720
738
|
(the one part of the site contract that is code, exactly as `itemBuilders`
|
|
@@ -925,22 +943,31 @@ system relationship it declares — see [`stats.systemVersion`](#statssystemvers
|
|
|
925
943
|
|
|
926
944
|
Each entry, in any of the four lists:
|
|
927
945
|
|
|
928
|
-
| Key (under `relationships.<kind>[]`)
|
|
929
|
-
|
|
|
930
|
-
| `relationships.systems[].id`
|
|
931
|
-
| `relationships.systems[].
|
|
932
|
-
| `relationships.systems[].
|
|
933
|
-
| `relationships.systems[].
|
|
934
|
-
| `relationships.systems[].
|
|
946
|
+
| Key (under `relationships.<kind>[]`) | Type | Required | Default |
|
|
947
|
+
| ---------------------------------------- | ------------------------------- | -------- | ------- |
|
|
948
|
+
| `relationships.systems[].id` | string | yes | — |
|
|
949
|
+
| `relationships.systems[].contentPackage` | string | no | the id |
|
|
950
|
+
| `relationships.systems[].type` | string | no | none |
|
|
951
|
+
| `relationships.systems[].manifest` | string | no | none |
|
|
952
|
+
| `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
|
|
953
|
+
| `relationships.systems[].itemCatalog` | boolean | no | `false` |
|
|
935
954
|
|
|
936
|
-
(the same
|
|
955
|
+
(the same keys apply under `requires[]`, `recommends[]` and
|
|
937
956
|
`conflicts[]`.)
|
|
938
957
|
|
|
939
958
|
> ``package-build config: `relationships.<kind>[<index>]` must be a mapping.``
|
|
940
959
|
|
|
941
960
|
> ``package-build config: `relationships.<kind>[<index>].id` must be a non-empty string.``
|
|
942
961
|
|
|
943
|
-
> ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, type, manifest, compatibility, itemCatalog).``
|
|
962
|
+
> ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog).``
|
|
963
|
+
|
|
964
|
+
`contentPackage` names what the other package's _content_ is called, where
|
|
965
|
+
that differs from its Foundry id. A note addresses a file by the content
|
|
966
|
+
package that owns it — `thalorna/assets/images/map.webp` — and the Foundry id
|
|
967
|
+
(`sohl-thalorna`) appears only in the install path that pathname resolves to.
|
|
968
|
+
Omit it where the two are the same word, which they are for every system:
|
|
969
|
+
|
|
970
|
+
> ``package-build config: `relationships.<kind>[<index>].contentPackage` must be a non-empty string.``
|
|
944
971
|
|
|
945
972
|
`itemCatalog` opts into extracting the named package's Item packs so the
|
|
946
973
|
actors pass can resolve embedded items this repository does not hold — off
|