@heroiclands/package-build 21.0.0 → 21.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/CONTENT.md +109 -7
  3. package/assets/fonts/libertinus/LibertinusMono-Regular.otf +0 -0
  4. package/assets/fonts/libertinus/LibertinusSans-Bold.otf +0 -0
  5. package/assets/fonts/libertinus/LibertinusSans-Italic.otf +0 -0
  6. package/assets/fonts/libertinus/LibertinusSans-Regular.otf +0 -0
  7. package/assets/fonts/libertinus/OFL.txt +94 -0
  8. package/assets/fonts/libertinus/provenance.yaml +11 -0
  9. package/assets/images/banners/afflictionbnr.webp +0 -0
  10. package/assets/images/banners/armorclothingbnr.webp +0 -0
  11. package/assets/images/banners/attributebnr.webp +0 -0
  12. package/assets/images/banners/containerbnr.webp +0 -0
  13. package/assets/images/banners/devdocsbnr.webp +0 -0
  14. package/assets/images/banners/miscgearbnr.webp +0 -0
  15. package/assets/images/banners/mysticalabilitybnr.webp +0 -0
  16. package/assets/images/banners/projectilebnr.webp +0 -0
  17. package/assets/images/banners/rulesbnr.webp +0 -0
  18. package/assets/images/banners/skillbnr.webp +0 -0
  19. package/assets/images/banners/traumabnr.webp +0 -0
  20. package/assets/images/banners/userguidebnr.webp +0 -0
  21. package/assets/images/banners/weapongearbnr.webp +0 -0
  22. package/assets/images/provenance.yaml +4 -0
  23. package/bin/content-build.mjs +19 -5
  24. package/ci/emit-asset-index.mjs +32 -0
  25. package/content-config.mjs +30 -1
  26. package/docs/api.md +100 -26
  27. package/docs/configuration.md +16 -7
  28. package/docs/content-format.md +512 -109
  29. package/engine/actor-compiler.mjs +58 -9
  30. package/engine/art-fields.mjs +292 -0
  31. package/engine/asset-index.mjs +397 -0
  32. package/engine/asset-types.mjs +192 -0
  33. package/engine/base-compiler.mjs +69 -1
  34. package/engine/bundles.mjs +5 -5
  35. package/engine/compile-corpus.mjs +4 -0
  36. package/engine/content-address.mjs +14 -2
  37. package/engine/content-charset.mjs +5 -1
  38. package/engine/content-embeds.mjs +314 -0
  39. package/engine/content-html.mjs +5 -1
  40. package/engine/content-icons.mjs +5 -1
  41. package/engine/content-images.mjs +26 -5
  42. package/engine/content-index.mjs +68 -33
  43. package/engine/content-links.mjs +164 -24
  44. package/engine/frontmatter-lint.mjs +36 -23
  45. package/engine/helpers.mjs +83 -31
  46. package/engine/index-records.mjs +63 -4
  47. package/engine/index.mjs +15 -0
  48. package/engine/infobox-registry.mjs +50 -3
  49. package/engine/infobox-render.mjs +4 -3
  50. package/engine/infobox.mjs +12 -8
  51. package/engine/item-compiler.mjs +10 -10
  52. package/engine/journals.mjs +3 -0
  53. package/engine/macros.mjs +16 -13
  54. package/engine/map-notes.mjs +29 -22
  55. package/engine/metadata-index.mjs +44 -36
  56. package/engine/note-vocabulary.mjs +71 -29
  57. package/engine/pack-router.mjs +14 -0
  58. package/engine/packagebuild-index.mjs +124 -0
  59. package/engine/packages.mjs +83 -0
  60. package/engine/pathnames.mjs +74 -0
  61. package/engine/pdf-build.mjs +133 -10
  62. package/engine/pdf-render.mjs +41 -41
  63. package/engine/scenes.mjs +12 -9
  64. package/engine/site-build.mjs +43 -16
  65. package/engine/site-index.mjs +10 -1
  66. package/engine/subtype-registry.mjs +21 -0
  67. package/engine/web-wikilinks.mjs +46 -12
  68. package/engine/wikilink-syntax.mjs +20 -2
  69. package/engine/wikilinks.mjs +15 -2
  70. package/hm3/actors.mjs +11 -38
  71. package/hm3/infobox.mjs +1 -1
  72. package/package.json +5 -2
  73. package/packagebuild-metadata.jsonl +13 -0
  74. package/sohl/actors.mjs +12 -86
  75. package/sohl/infobox.mjs +1 -1
  76. package/sohl/note-schemas.mjs +4 -16
  77. package/types/content-config.d.mts +8 -0
  78. package/types/engine/actor-compiler.d.mts +29 -0
  79. package/types/engine/art-fields.d.mts +192 -0
  80. package/types/engine/asset-index.d.mts +92 -0
  81. package/types/engine/asset-types.d.mts +110 -0
  82. package/types/engine/base-compiler.d.mts +42 -1
  83. package/types/engine/compile-corpus.d.mts +4 -1
  84. package/types/engine/content-charset.d.mts +4 -0
  85. package/types/engine/content-embeds.d.mts +218 -0
  86. package/types/engine/content-html.d.mts +4 -0
  87. package/types/engine/content-icons.d.mts +4 -0
  88. package/types/engine/content-images.d.mts +15 -2
  89. package/types/engine/content-index.d.mts +27 -15
  90. package/types/engine/frontmatter-lint.d.mts +11 -14
  91. package/types/engine/helpers.d.mts +7 -2
  92. package/types/engine/index-records.d.mts +44 -3
  93. package/types/engine/index.d.mts +5 -0
  94. package/types/engine/infobox-registry.d.mts +36 -1
  95. package/types/engine/infobox.d.mts +6 -4
  96. package/types/engine/macros.d.mts +9 -5
  97. package/types/engine/map-notes.d.mts +10 -6
  98. package/types/engine/metadata-index.d.mts +2 -17
  99. package/types/engine/note-vocabulary.d.mts +30 -0
  100. package/types/engine/packagebuild-index.d.mts +56 -0
  101. package/types/engine/packages.d.mts +62 -0
  102. package/types/engine/pathnames.d.mts +41 -81
  103. package/types/engine/pdf-build.d.mts +58 -1
  104. package/types/engine/pdf-render.d.mts +3 -3
  105. package/types/engine/scenes.d.mts +2 -1
  106. package/types/engine/site-build.d.mts +5 -1
  107. package/types/engine/site-index.d.mts +4 -1
  108. package/types/engine/subtype-registry.d.mts +20 -0
  109. package/types/engine/web-wikilinks.d.mts +14 -4
  110. package/types/engine/wikilink-syntax.d.mts +13 -1
  111. package/types/engine/wikilinks.d.mts +7 -1
  112. package/types/sohl/actors.d.mts +0 -1
package/sohl/actors.mjs CHANGED
@@ -46,13 +46,7 @@
46
46
  * @module
47
47
  */
48
48
 
49
- import {
50
- sohlField,
51
- resolveName,
52
- resolveImg,
53
- systemTemplatePriority,
54
- folderField,
55
- } from "../engine/helpers.mjs";
49
+ import { sohlField, resolveName, systemTemplatePriority, folderField } from "../engine/helpers.mjs";
56
50
  import { openingMasteryLevel } from "./skill-base.mjs";
57
51
  import { SystemActorCompiler, renderSection } from "../engine/actor-compiler.mjs";
58
52
  // Which Foundry Actor subtype a note's `type` compiles into. Looked up in the
@@ -63,14 +57,6 @@ import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
63
57
  // verbatim, and `sohl.img` / `sohl.effects` / `sohl.flags` overriding their
64
58
  // shared top-level forms for this system alone.
65
59
  import { blockProperty, mergeSystemData } from "../engine/system-block.mjs";
66
- import { readField, retiredTopLevelKey } from "../engine/field-spec.mjs";
67
- // The retirement window's reports, shared with the frontmatter lint so the two
68
- // cannot say different things about the same key.
69
- import {
70
- legacyKeyMessage,
71
- locateFrontmatterKey,
72
- retiredTopLevelMessage,
73
- } from "../engine/retired-fields.mjs";
74
60
 
75
61
  /**
76
62
  * The system this pass compiles for — the block its notes write.
@@ -89,38 +75,6 @@ const DEFAULT_IMG = {
89
75
  being: "systems/sohl/assets/icons/game-icons/delapouite/person.svg",
90
76
  };
91
77
 
92
- /**
93
- * The being's sheet portrait — the one row of the content format's actor
94
- * mapping table that is authored rather than derived.
95
- *
96
- * **Declared, because the position is not a spelling anyone can guess.** This
97
- * was read with `blockProperty(fm, SYSTEM, "portrait")`, which knows the block
98
- * and the note's top level and nothing else — so `data.portrait`, the position
99
- * the specification names and `sohl-thalorna` writes on 646 beings, was
100
- * invisible, and `?? defaultImg` on the next line turned every miss into the
101
- * generic person icon rather than into a complaint. Going through
102
- * {@link module:engine/field-spec.readField} is what makes the mapping table
103
- * executable here as it already is for HM3's `data.species`.
104
- *
105
- * `img` is deliberately **not** declared beside it: the mapping table keeps a
106
- * note's token art at the top level, so `blockProperty` is the whole of its
107
- * resolution and there is no `data.img` to reach for.
108
- *
109
- * @type {import("../engine/field-spec.mjs").FieldSpec}
110
- */
111
- const PORTRAIT_FIELD = Object.freeze({
112
- name: "data.portrait",
113
- legacyKey: "portrait",
114
- to: "portrait",
115
- shape: "path",
116
- // The two empties survive, because the caller's `?? defaultImg` is what
117
- // tells them apart: `null` and an absent key mean "no art named, default
118
- // me", `""` means "ship blank on purpose".
119
- read: (raw) => resolveImg(raw),
120
- default: null,
121
- describe: "Path to the portrait image.",
122
- });
123
-
124
78
  /**
125
79
  * The default art for an actor subtype.
126
80
  *
@@ -366,43 +320,16 @@ export class Actors extends SystemActorCompiler {
366
320
  absPath: this.currentNote?.absPath,
367
321
  })
368
322
  );
369
- const defaultImg = defaultActorImg(subType);
323
+ // The being's own default sits above the subtype's: only the note's
324
+ // tags say whether it is a person or a creature, and only this pass
325
+ // reads them.
326
+ const art = this.actorArt(fm, defaultActorImg(subType));
370
327
 
371
328
  const items = this.buildEmbeddedItems(itemsMap, id, fm, ctx);
372
329
 
373
330
  const { value: authoredFolder, isAddress } = folderField(fm);
374
331
  const folder = this.folderResolver(authoredFolder, { isAddress });
375
332
 
376
- // The two retiring positions a declared field may be read from.
377
- // **Warnings**, on the pattern every retirement in this package
378
- // follows: the note compiles to the correct document either way, so
379
- // reddening a tree over one would refuse before the sweep rather than
380
- // after it. What they buy is a count — without one, nothing says which
381
- // position a value came from, and a default is indistinguishable from
382
- // a miss.
383
- const portraitReports = {
384
- block: SYSTEM,
385
- onLegacyKey: (field) =>
386
- this.noteWarn(
387
- legacyKeyMessage(SYSTEM, field),
388
- locateFrontmatterKey(this.currentNote?.absPath, field.legacyKey),
389
- ),
390
- // Anchored at column 1: the two positions share a spelling here —
391
- // `sohl.portrait` and `portrait` — so a locator that took the first
392
- // match would point at the block key while the message named the
393
- // top-level one.
394
- onRetiredTopLevel: (field) =>
395
- this.noteWarn(
396
- retiredTopLevelMessage(field),
397
- locateFrontmatterKey(
398
- this.currentNote?.absPath,
399
- retiredTopLevelKey(field),
400
- undefined,
401
- { topLevel: true },
402
- ),
403
- ),
404
- };
405
-
406
333
  const system = {
407
334
  // The frontmatter shortcode is the actor's stable `(type, shortcode)`
408
335
  // key — and, for a being that is an archetype, its archetype
@@ -414,11 +341,11 @@ export class Actors extends SystemActorCompiler {
414
341
  // with the schema, since an undeclared `system` key is discarded at
415
342
  // construction without a warning.
416
343
  templatePriority: systemTemplatePriority(fm, ctx),
417
- // Nullish, not `||`: a note that names no portrait gets the
418
- // subtype's default, one that writes `""` ships blank on purpose.
419
- // Resolved through the declaration so `data.portrait` is reached at
420
- // all see {@link PORTRAIT_FIELD}.
421
- portrait: readField(PORTRAIT_FIELD, fm, portraitReports) ?? defaultImg,
344
+ // `system.portrait` is **not** written here. A picture of the
345
+ // subject is a picture, so a being's portrait is the lead image of
346
+ // its `{#appearance}` section — which is the markup below and the
347
+ // key that used to declare one is not a key. With no authored
348
+ // source left, the field keeps the schema's own initial.
422
349
  appearance: renderSection(body || "", "appearance"),
423
350
  dossier: renderSection(body || "", "dossier"),
424
351
  };
@@ -470,8 +397,7 @@ export class Actors extends SystemActorCompiler {
470
397
  return {
471
398
  name,
472
399
  type: subType,
473
- // Nullish, not `||` — see the portrait above.
474
- img: resolveImg(blockProperty(fm, SYSTEM, "img")) ?? defaultImg,
400
+ img: art.img,
475
401
  _id: id,
476
402
  system,
477
403
  items,
@@ -479,7 +405,7 @@ export class Actors extends SystemActorCompiler {
479
405
  name,
480
406
  displayName: 0,
481
407
  actorLink: false,
482
- texture: { src: resolveImg(blockProperty(fm, SYSTEM, "img")) ?? defaultImg },
408
+ texture: { src: art.token },
483
409
  width: 1,
484
410
  height: 1,
485
411
  sight: { enabled: false },
package/sohl/infobox.mjs CHANGED
@@ -67,7 +67,7 @@ import { NOTE_SCHEMAS } from "./note-schemas.mjs";
67
67
  import { GEAR_TYPE_TO_KEY } from "./being-info.mjs";
68
68
 
69
69
  /** What this system's box is called. @type {string} */
70
- export const SOHL_INFOBOX_TITLE = "SoHL";
70
+ export const SOHL_INFOBOX_TITLE = "Song of Heroic Lands";
71
71
 
72
72
  /**
73
73
  * What a value nobody stated is shown as, where showing nothing would be the
@@ -146,26 +146,14 @@ const BEING_FIELDS = Object.freeze([
146
146
  * business; their authored vocabulary is the same, so they share one
147
147
  * declaration rather than three copies that could drift.
148
148
  *
149
- * `img` is the one required field — the compiler refuses a map note without it,
150
- * since a scene with no background is not a map. It was spelled `image` and
151
- * read from the `sohl:` block; both were read while the trees were
152
- * swept; that retirement has since completed and `image` is gone.
149
+ * `data.bgImage` is the one required piece of art — the compiler refuses a map
150
+ * note without it, since a scene with no background is not a map. It is
151
+ * declared in the note vocabulary rather than here, because it is an art slot
152
+ * like every other and a Scene has no `img` for the shared key to reach.
153
153
  *
154
154
  * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
155
155
  */
156
156
  const MAP_FIELDS = Object.freeze([
157
- {
158
- name: "img",
159
- ...STRING,
160
- required: true,
161
- // Art is not system-specific — a Scene is a core Foundry document, and
162
- // every other note type carries its `img` at the note's top level.
163
- shared: true,
164
- describe:
165
- "The scene's background image. Owned by whichever package its first segment " +
166
- "names — `systems/…` and `modules/…` are emitted unchanged, anything else is " +
167
- "this package's own and is rooted under its assets.",
168
- },
169
157
  {
170
158
  name: "dimensions",
171
159
  ...LIST,
@@ -66,6 +66,7 @@ export const PACKAGE_KINDS: readonly ["systems", "modules", "documentation"];
66
66
  export const DOCUMENTATION_KIND: string;
67
67
  export namespace DEFAULT_PATHS {
68
68
  let content: "assets/content";
69
+ let assets: "assets";
69
70
  let contentIndex: "build/content-index";
70
71
  let packJson: "build/packs-json";
71
72
  let stage: "build/stage/packs";
@@ -299,6 +300,12 @@ export type PathsInput = {
299
300
  * Content tree root.
300
301
  */
301
302
  content?: string | undefined;
303
+ /**
304
+ * The asset roots' parent — the directory
305
+ * holding `icons/`, `images/` and
306
+ * `audio/`.
307
+ */
308
+ assets?: string | undefined;
302
309
  /**
303
310
  * Where `content-index` writes this
304
311
  * package's note index. Outbound, and a
@@ -338,6 +345,7 @@ export type PathsInput = {
338
345
  */
339
346
  export type ResolvedPaths = {
340
347
  content: string;
348
+ assets: string;
341
349
  contentIndex: string;
342
350
  packJson: string;
343
351
  stage: string;
@@ -182,6 +182,29 @@ export class SystemActorCompiler extends BasePackCompiler {
182
182
  itemsSourceDirs?: never[] | undefined;
183
183
  foreignSourceDirs?: never[] | undefined;
184
184
  });
185
+ /**
186
+ * The actor's two pieces of art, resolved, with the being default beneath
187
+ * them.
188
+ *
189
+ * **The default is chosen from the note's tags**, which only a compiler can
190
+ * read: a `character` falls back to one file and a `creature` to another,
191
+ * and both are addresses in the package that ships them. A tree whose index
192
+ * cannot answer that address — a dependency not yet fetched — falls through
193
+ * to the subtype's own default, so the document is never left with no art.
194
+ *
195
+ * `tokenIcon` unset follows `icon`, and the fallback is applied after
196
+ * resolution rather than before: a note naming an icon and no token icon
197
+ * means "the token wears the profile art", not "the token wears the
198
+ * default".
199
+ *
200
+ * @param {object} fm - The note's frontmatter.
201
+ * @param {string} fallback - The subtype's own default art path.
202
+ * @returns {{img: string, token: string}} The two paths.
203
+ */
204
+ actorArt(fm: object, fallback: string): {
205
+ img: string;
206
+ token: string;
207
+ };
185
208
  /** @type {readonly string[]} */
186
209
  itemsSourceDirs: readonly string[];
187
210
  foreignSourceDirs: any;
@@ -260,6 +283,12 @@ export class SystemActorCompiler extends BasePackCompiler {
260
283
  * every embedded id exactly where it was.
261
284
  * Returns null if the descriptor cannot be resolved.
262
285
  *
286
+ * **An entry's `data:` is the authoring container, not a document field.**
287
+ * Its art slot is read here and the container itself goes no further, so an
288
+ * entry writing `data.icon` reaches the document as a resolved `img` and an
289
+ * entry writing none takes its type's default — the same two rules an item
290
+ * note compiles under, reached by the same functions.
291
+ *
263
292
  * @param {Map<string, object>} itemsMap - The predefined items, by address.
264
293
  * @param {string} actorId - The owning actor's id, seeding embedded ids.
265
294
  * @param {string} type - The **note** type the reference names.
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The art slot one key names, or `undefined`.
3
+ *
4
+ * @param {unknown} key - The key under `data:`.
5
+ * @returns {ArtSlot|undefined} The slot.
6
+ */
7
+ export function artSlot(key: unknown): ArtSlot | undefined;
8
+ /**
9
+ * The address an authored art value names.
10
+ *
11
+ * @param {string} value - The value as authored.
12
+ * @param {string} defaultType - The type the field declares.
13
+ * @returns {string} A written address, which may be partial.
14
+ */
15
+ export function artTarget(value: string, defaultType: string): string;
16
+ /**
17
+ * The address space an asset reference resolves against, from a corpus.
18
+ *
19
+ * Shaped exactly as {@link module:engine/wikilinks.buildWikilinkIndex}'s result
20
+ * is in the parts a resolver reads, so the site, the book and the pack compilers
21
+ * answer one authored address the same way.
22
+ *
23
+ * **The note types belong in `types` as well as the asset ones.** Without them
24
+ * `being-thorn` does not parse as an address at all, and an embed naming a note
25
+ * is reported as an unknown type on one surface and as the wrong kind of type on
26
+ * another — one mistake, two verdicts, which is what the shared vocabulary
27
+ * exists to prevent.
28
+ *
29
+ * @param {readonly object[]} records - The corpus, from
30
+ * {@link module:engine/content-index.indexRecordsFor}.
31
+ * @param {object} [opts]
32
+ * @param {object} [opts.config] - The resolved build configuration.
33
+ * @param {{index?: Map<string, object>, packages?: Iterable<string>}} [opts.foreign] -
34
+ * The vendored indexes a dependency published.
35
+ * @param {Iterable<string>} [opts.types] - The note types this tree knows.
36
+ * @returns {object} The index.
37
+ */
38
+ export function assetAddressIndex(records?: readonly object[], { config, foreign, types }?: {
39
+ config?: object | undefined;
40
+ foreign?: {
41
+ index?: Map<string, object>;
42
+ packages?: Iterable<string>;
43
+ } | undefined;
44
+ types?: Iterable<string> | undefined;
45
+ }): object;
46
+ /**
47
+ * The asset one authored value names, or why it names none.
48
+ *
49
+ * The whole lookup in one place, because two callers need it and they need
50
+ * different halves of the answer: an art slot needs the record, and an embed
51
+ * needs to tell an address that resolves to nothing from one that reaches the
52
+ * wrong *kind* of type. Those are different mistakes with different fixes, and a
53
+ * single `null` would collapse them into one message.
54
+ *
55
+ * Local files answer first and foreign ones after, which is an ordering of maps
56
+ * rather than a precedence rule: an address carries its own package, so the two
57
+ * cannot both hold one.
58
+ *
59
+ * @param {object} index - From {@link module:engine/wikilinks.buildWikilinkIndex},
60
+ * or the equivalent the site and the book build.
61
+ * @param {unknown} value - The value as authored.
62
+ * @param {string} defaultType - The type a bare value takes.
63
+ * @returns {{record: {package: string, asset: {path: string}}, pathname: string}
64
+ * |{record: null, reason: string, type?: string}} The asset and the pathname
65
+ * it is at, or a reason from
66
+ * {@link module:engine/wikilink-syntax.LINK_FINDING_REASONS}.
67
+ */
68
+ export function readAssetAddress(index: object, value: unknown, defaultType: string): {
69
+ record: {
70
+ package: string;
71
+ asset: {
72
+ path: string;
73
+ };
74
+ };
75
+ pathname: string;
76
+ } | {
77
+ record: null;
78
+ reason: string;
79
+ type?: string;
80
+ };
81
+ /**
82
+ * The index record an art value resolves to, or `null`.
83
+ *
84
+ * @param {object} index - From {@link module:engine/wikilinks.buildWikilinkIndex}.
85
+ * @param {unknown} value - The value as authored.
86
+ * @param {string} defaultType - The type the field declares.
87
+ * @returns {{package: string, asset: {path: string}}|null} The record.
88
+ */
89
+ export function resolveArtRecord(index: object, value: unknown, defaultType: string): {
90
+ package: string;
91
+ asset: {
92
+ path: string;
93
+ };
94
+ } | null;
95
+ /**
96
+ * The pathname an art value names, in the form an authored one takes.
97
+ *
98
+ * Handing the result to {@link module:engine/helpers.resolveImg} is what puts an
99
+ * art address and a body image through one ownership rule. The two empties
100
+ * survive it unchanged: `null` and an absent key mean *no art named, apply the
101
+ * default*, and `""` means *ship blank on purpose*.
102
+ *
103
+ * @param {object} index - From {@link module:engine/wikilinks.buildWikilinkIndex}.
104
+ * @param {unknown} value - The value as authored.
105
+ * @param {string} defaultType - The type the field declares.
106
+ * @returns {{pathname: string|null, resolved: boolean}} The pathname, and
107
+ * whether an address was actually answered — which tells a caller applying a
108
+ * default apart from one whose address named nothing.
109
+ */
110
+ export function artPathname(index: object, value: unknown, defaultType: string): {
111
+ pathname: string | null;
112
+ resolved: boolean;
113
+ };
114
+ /**
115
+ * The default art address a being's own tags choose, or `null`.
116
+ *
117
+ * `character` means a **person**, not a human, and `creature` everything else;
118
+ * a being carries exactly one of the two, which is what makes the choice a
119
+ * lookup rather than a precedence rule.
120
+ *
121
+ * @param {object} fm - The note's frontmatter.
122
+ * @returns {string|null} The address, or `null` for a note carrying neither tag.
123
+ */
124
+ export function beingDefaultArt(fm: object): string | null;
125
+ /**
126
+ * What an unresolved art address is reported as.
127
+ *
128
+ * One wording, so the four compilers that can meet the case do not each invent
129
+ * their own.
130
+ *
131
+ * @param {string} key - The key that was authored.
132
+ * @param {unknown} value - The value it carried.
133
+ * @param {string} defaultType - The type the field declares.
134
+ * @returns {string} The message.
135
+ */
136
+ export function unresolvedArtMessage(key: string, value: unknown, defaultType: string): string;
137
+ /**
138
+ * One art slot: the key a note authors, and the type a bare value takes.
139
+ *
140
+ * @typedef {object} ArtSlot
141
+ * @property {string} key - The key under `data:`.
142
+ * @property {string} type - The asset type a bare shortcode defaults to.
143
+ * @property {boolean} document - Whether the slot reaches a compiled document.
144
+ * @property {string} describe - One line, for the author-facing reference.
145
+ */
146
+ /**
147
+ * The four art slots, in the order the specification tabulates them.
148
+ *
149
+ * `banner` is the one that reaches no compiled document: it is the page's hero
150
+ * image, read by the site and by the book's section plates and by nothing else.
151
+ * That is what `document: false` states, and it is why the inert-art check
152
+ * skips it — a key that is *meant* to reach no document is not an inert key.
153
+ *
154
+ * @type {readonly ArtSlot[]}
155
+ */
156
+ export const ART_SLOTS: readonly ArtSlot[];
157
+ /**
158
+ * The art a being falls back to, by the kind it is tagged.
159
+ *
160
+ * Both files ship in `sohl`, under `assets/icons/other/`, and both are named
161
+ * here as addresses rather than as paths for the reason every art reference is:
162
+ * the address resolves to a record that carries the owning package, so a
163
+ * package borrowing the default gets the same file the system ships.
164
+ *
165
+ * **Only the compiler can choose between them**, because only the compiler
166
+ * reads the note's tags. A schema default is the last resort beneath this one,
167
+ * and covers a world document created by hand, which no note describes.
168
+ *
169
+ * @type {Readonly<Record<string, string>>}
170
+ */
171
+ export const BEING_DEFAULT_ART: Readonly<Record<string, string>>;
172
+ /**
173
+ * One art slot: the key a note authors, and the type a bare value takes.
174
+ */
175
+ export type ArtSlot = {
176
+ /**
177
+ * - The key under `data:`.
178
+ */
179
+ key: string;
180
+ /**
181
+ * - The asset type a bare shortcode defaults to.
182
+ */
183
+ type: string;
184
+ /**
185
+ * - Whether the slot reaches a compiled document.
186
+ */
187
+ document: boolean;
188
+ /**
189
+ * - One line, for the author-facing reference.
190
+ */
191
+ describe: string;
192
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Read a package's asset trees into index records.
3
+ *
4
+ * @param {string} assetsBase - The package's asset directory, the three roots'
5
+ * parent. A directory that does not exist yields nothing: a package with no
6
+ * art is ordinary.
7
+ * @param {object} options - Options.
8
+ * @param {string} options.contentPackage - The package the trees belong to.
9
+ * @param {object[]} [options.problems] - Supplied by a **reader**: a file that
10
+ * cannot be addressed is pushed here as a diagnostic and skipped. Omitted, it
11
+ * throws — the contract the emitter needs, since an index missing an asset
12
+ * asserts that the address does not exist.
13
+ * @returns {Array<Record<string, any>>} One record per addressable file, in walk
14
+ * order; the caller imposes the index's total order.
15
+ * @throws {Error} When a file cannot be addressed and no `problems` array was
16
+ * supplied. `file` rides on the error.
17
+ */
18
+ export function collectAssetRecords(assetsBase: string, { contentPackage, problems }: {
19
+ contentPackage: string;
20
+ problems?: object[] | undefined;
21
+ }): Array<Record<string, any>>;
22
+ /**
23
+ * The file a directory records provenance for its subtree in.
24
+ *
25
+ * @type {string}
26
+ */
27
+ export const PROVENANCE_FILE: string;
28
+ /**
29
+ * The suffix a per-file provenance sidecar carries.
30
+ *
31
+ * Appended to the **whole** filename, extension included — `anvil.svg.yaml`,
32
+ * not `anvil.yaml` — because the address holds one file whose format is free to
33
+ * change, and a sidecar named after the address alone would be orphaned the day
34
+ * an SVG became a WebP without anything saying so.
35
+ *
36
+ * @type {string}
37
+ */
38
+ export const PROVENANCE_SIDECAR_SUFFIX: string;
39
+ /**
40
+ * One field of the `asset` block.
41
+ *
42
+ * @typedef {object} AssetRecordField
43
+ * @property {string} name - The key inside `asset`.
44
+ * @property {"walk"|"provenance"} from - Where the value comes from: the walk
45
+ * itself, or the provenance record resolved for the address.
46
+ * @property {string} describe - One line, for the author-facing reference.
47
+ */
48
+ /**
49
+ * Every field an `asset` block carries, in the order it is documented.
50
+ *
51
+ * **The emitter builds a record from this list**, rather than from a literal
52
+ * that a second list somewhere else would have to be kept in step with. So a
53
+ * field added here is emitted, a field removed here stops being emitted, and the
54
+ * completeness guard derives what it checks from the same declaration instead of
55
+ * hand-copying it.
56
+ *
57
+ * Every field is always present, blank where nothing states it. A fixed record
58
+ * shape is what lets a consumer read `asset.license` without branching on
59
+ * whether the package happened to record one, and blank is the honest answer to
60
+ * "what does this package say about where this file came from" when it says
61
+ * nothing.
62
+ *
63
+ * @type {readonly AssetRecordField[]}
64
+ */
65
+ export const ASSET_RECORD_FIELDS: readonly AssetRecordField[];
66
+ /**
67
+ * The keys a provenance file may declare.
68
+ *
69
+ * Derived from {@link ASSET_RECORD_FIELDS} rather than restated, so the file
70
+ * format and the record cannot disagree about which keys exist.
71
+ *
72
+ * @type {ReadonlySet<string>}
73
+ */
74
+ export const PROVENANCE_KEYS: ReadonlySet<string>;
75
+ /**
76
+ * One field of the `asset` block.
77
+ */
78
+ export type AssetRecordField = {
79
+ /**
80
+ * - The key inside `asset`.
81
+ */
82
+ name: string;
83
+ /**
84
+ * - Where the value comes from: the walk
85
+ * itself, or the provenance record resolved for the address.
86
+ */
87
+ from: "walk" | "provenance";
88
+ /**
89
+ * - One line, for the author-facing reference.
90
+ */
91
+ describe: string;
92
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Whether a type name addresses a file rather than a note.
3
+ *
4
+ * The one test the rewrite scoping rests on: a rewrite rule may substitute an
5
+ * asset and nothing else, so a fourth asset type is covered by this answer
6
+ * rather than by editing a list somewhere else.
7
+ *
8
+ * @param {unknown} type - The type name.
9
+ * @returns {boolean} True for `icon`, `image` or `audio`.
10
+ */
11
+ export function isAssetType(type: unknown): boolean;
12
+ /**
13
+ * The asset type a root directory declares, or `undefined`.
14
+ *
15
+ * @param {unknown} root - A directory name below `paths.assets`.
16
+ * @returns {AssetType|undefined} The type it holds.
17
+ */
18
+ export function assetTypeOfRoot(root: unknown): AssetType | undefined;
19
+ /**
20
+ * Whether a filename can be an address at all.
21
+ *
22
+ * A shortcode is lowercase alphanumerics, so a version string, a hyphen or a
23
+ * date stamp in a basename means the file cannot be addressed. The build says so
24
+ * rather than inventing a shortcode for it.
25
+ *
26
+ * @param {string} shortcode - The basename with its extension removed.
27
+ * @returns {boolean} Whether it matches {@link ADDRESS_SEGMENT_PATTERN}.
28
+ */
29
+ export function isAssetShortcode(shortcode: string): boolean;
30
+ /**
31
+ * The `<system>` segment every asset address carries.
32
+ *
33
+ * Spelled here rather than imported from `engine/systems.mjs` so this module
34
+ * stays a leaf. The two are held to one value by the address round-trip guard,
35
+ * which reads both.
36
+ *
37
+ * @type {string}
38
+ */
39
+ export const ASSET_SYSTEM: string;
40
+ /**
41
+ * File extensions that make a file a picture, lowercase and dot-led.
42
+ *
43
+ * Foundry's own `IMAGE_FILE_EXTENSIONS`, because these files are installed into
44
+ * a Foundry data directory and a format Foundry will not display is not one this
45
+ * toolchain should hand it an address for.
46
+ *
47
+ * @type {readonly string[]}
48
+ */
49
+ export const IMAGE_EXTENSIONS: readonly string[];
50
+ /**
51
+ * File extensions that make a file a sound, lowercase and dot-led.
52
+ *
53
+ * Foundry's own `AUDIO_FILE_EXTENSIONS`, for the reason above.
54
+ *
55
+ * @type {readonly string[]}
56
+ */
57
+ export const AUDIO_EXTENSIONS: readonly string[];
58
+ /**
59
+ * One asset type: what it is called, which directory holds it, and which files
60
+ * in that directory are assets of it.
61
+ *
62
+ * @typedef {object} AssetType
63
+ * @property {string} type - The type name, and the third segment of an address.
64
+ * @property {string} root - The directory below `paths.assets` that holds it.
65
+ * @property {readonly string[]} extensions - Lowercase, dot-led.
66
+ * @property {string} describe - One line, for the author-facing reference.
67
+ */
68
+ /**
69
+ * The three asset types, in address order.
70
+ *
71
+ * The directory is named for what it holds and the type for what an address
72
+ * reaches, so the two differ by a letter and the mapping is **declared** rather
73
+ * than derived from the name.
74
+ *
75
+ * `icon` and `image` are two types rather than one because an icon has to stay
76
+ * coherent drawn into a 32×32 slot while an image is unbounded — a fitness
77
+ * property of the asset itself. They therefore have separate shortcode
78
+ * namespaces, and `icon-anvil` and `image-anvil` are different addresses.
79
+ *
80
+ * @type {readonly AssetType[]}
81
+ */
82
+ export const ASSET_TYPES: readonly AssetType[];
83
+ /**
84
+ * Every asset type name.
85
+ *
86
+ * @type {ReadonlySet<string>}
87
+ */
88
+ export const ASSET_TYPE_NAMES: ReadonlySet<string>;
89
+ /**
90
+ * One asset type: what it is called, which directory holds it, and which files
91
+ * in that directory are assets of it.
92
+ */
93
+ export type AssetType = {
94
+ /**
95
+ * - The type name, and the third segment of an address.
96
+ */
97
+ type: string;
98
+ /**
99
+ * - The directory below `paths.assets` that holds it.
100
+ */
101
+ root: string;
102
+ /**
103
+ * - Lowercase, dot-led.
104
+ */
105
+ extensions: readonly string[];
106
+ /**
107
+ * - One line, for the author-facing reference.
108
+ */
109
+ describe: string;
110
+ };