@heroiclands/package-build 22.21.0 → 22.23.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 (152) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/assets/styles/content-base.css +162 -0
  3. package/bin/content-commands.mjs +77 -17
  4. package/bin/package-build.mjs +15 -2
  5. package/ci/content-format-reference.mjs +68 -1
  6. package/config.mjs +26 -0
  7. package/content-config.mjs +153 -6
  8. package/docs/api.md +120 -89
  9. package/docs/authoring/assets.md +14 -0
  10. package/docs/authoring/dates-and-calendars.md +41 -3
  11. package/docs/authoring/frontmatter.md +2 -2
  12. package/docs/authoring/links-and-markup.md +325 -35
  13. package/docs/commands.md +12 -10
  14. package/docs/configuration.md +117 -14
  15. package/docs/content-format.md +4 -2
  16. package/docs/guides/book.md +5 -4
  17. package/docs/guides/reference-pages.md +15 -0
  18. package/docs/reference/format-details.md +545 -104
  19. package/docs/reference/note-types.md +30 -13
  20. package/engine/actor-compiler.mjs +23 -0
  21. package/engine/anchored-sections.mjs +35 -4
  22. package/engine/anchors.mjs +26 -30
  23. package/engine/art-fields.mjs +118 -0
  24. package/engine/art-slots.mjs +7 -1
  25. package/engine/asset-index.mjs +160 -10
  26. package/engine/asset-replacement.mjs +93 -0
  27. package/engine/asset-types.mjs +240 -0
  28. package/engine/base-compiler.mjs +139 -22
  29. package/engine/base-styles.mjs +149 -0
  30. package/engine/bundles.mjs +13 -2
  31. package/engine/compendiums.mjs +8 -1
  32. package/engine/content-blocks.mjs +410 -0
  33. package/engine/content-embeds.mjs +19 -0
  34. package/engine/content-figures.mjs +468 -0
  35. package/engine/content-footnotes.mjs +197 -0
  36. package/engine/content-format-check.mjs +15 -12
  37. package/engine/content-format.yaml +1 -1
  38. package/engine/content-images.mjs +126 -25
  39. package/engine/content-index.mjs +38 -12
  40. package/engine/content-links.mjs +14 -6
  41. package/engine/content-tables.mjs +45 -4
  42. package/engine/content-tasklists.mjs +189 -0
  43. package/engine/docs-checks.mjs +4 -4
  44. package/engine/extension-attributes.mjs +48 -2
  45. package/engine/field-reference.mjs +31 -10
  46. package/engine/field-spec.mjs +10 -8
  47. package/engine/foreign-catalog.mjs +143 -18
  48. package/engine/frontmatter-lint.mjs +120 -65
  49. package/engine/frontmatter.mjs +3 -3
  50. package/engine/heading-attributes.mjs +321 -0
  51. package/engine/helpers.mjs +109 -41
  52. package/engine/homepage.mjs +8 -8
  53. package/engine/index.mjs +3 -0
  54. package/engine/infobox-registry.mjs +4 -1
  55. package/engine/infobox-render.mjs +15 -3
  56. package/engine/infobox.mjs +129 -15
  57. package/engine/itinerary-scenes.mjs +4 -10
  58. package/engine/journals.mjs +294 -50
  59. package/engine/lore-events.mjs +197 -0
  60. package/engine/macros.mjs +1 -1
  61. package/engine/markdown-expressions.mjs +434 -59
  62. package/engine/note-dates.mjs +354 -12
  63. package/engine/note-frontmatter.mjs +7 -16
  64. package/engine/note-vocabulary.mjs +183 -42
  65. package/engine/page-lists.mjs +358 -0
  66. package/engine/pathnames.mjs +36 -0
  67. package/engine/pdf-build.mjs +431 -74
  68. package/engine/pdf-images.mjs +64 -0
  69. package/engine/pdf-render.mjs +563 -66
  70. package/engine/rank-ladder.mjs +198 -0
  71. package/engine/reckoning-markers.mjs +1 -1
  72. package/engine/retired-fields.mjs +8 -13
  73. package/engine/scenes.mjs +26 -7
  74. package/engine/site-build.mjs +166 -23
  75. package/engine/site-config.mjs +10 -1
  76. package/engine/site-index.mjs +90 -3
  77. package/engine/site-preview.mjs +20 -7
  78. package/engine/social-ties.mjs +52 -14
  79. package/engine/sql-tables.mjs +66 -9
  80. package/engine/standings.mjs +89 -6
  81. package/engine/system-block.mjs +56 -30
  82. package/engine/web-wikilinks.mjs +48 -1
  83. package/hm3/actors.mjs +8 -5
  84. package/manifest.mjs +43 -24
  85. package/package.json +1 -1
  86. package/packagebuild-metadata.jsonl +13 -13
  87. package/sohl/actors.mjs +22 -99
  88. package/sohl/item-fields.mjs +5 -5
  89. package/sohl/items.mjs +1 -1
  90. package/sohl/note-schemas.mjs +11 -21
  91. package/types/config.d.mts +9 -0
  92. package/types/content-config.d.mts +48 -1
  93. package/types/engine/actor-compiler.d.mts +11 -0
  94. package/types/engine/anchored-sections.d.mts +24 -0
  95. package/types/engine/anchors.d.mts +9 -4
  96. package/types/engine/art-fields.d.mts +64 -0
  97. package/types/engine/asset-index.d.mts +43 -0
  98. package/types/engine/asset-replacement.d.mts +22 -0
  99. package/types/engine/asset-types.d.mts +18 -0
  100. package/types/engine/base-compiler.d.mts +19 -17
  101. package/types/engine/base-styles.d.mts +93 -0
  102. package/types/engine/content-blocks.d.mts +87 -0
  103. package/types/engine/content-embeds.d.mts +16 -0
  104. package/types/engine/content-figures.d.mts +101 -0
  105. package/types/engine/content-footnotes.d.mts +80 -0
  106. package/types/engine/content-format-check.d.mts +8 -6
  107. package/types/engine/content-images.d.mts +78 -12
  108. package/types/engine/content-index.d.mts +8 -2
  109. package/types/engine/content-tables.d.mts +12 -4
  110. package/types/engine/content-tasklists.d.mts +72 -0
  111. package/types/engine/extension-attributes.d.mts +22 -1
  112. package/types/engine/field-reference.d.mts +21 -0
  113. package/types/engine/field-spec.d.mts +20 -16
  114. package/types/engine/foreign-catalog.d.mts +39 -0
  115. package/types/engine/frontmatter.d.mts +1 -1
  116. package/types/engine/heading-attributes.d.mts +179 -0
  117. package/types/engine/helpers.d.mts +29 -10
  118. package/types/engine/homepage.d.mts +7 -7
  119. package/types/engine/index.d.mts +1 -0
  120. package/types/engine/infobox-registry.d.mts +4 -1
  121. package/types/engine/infobox-render.d.mts +6 -1
  122. package/types/engine/infobox.d.mts +6 -0
  123. package/types/engine/journals.d.mts +41 -12
  124. package/types/engine/lore-events.d.mts +14 -0
  125. package/types/engine/markdown-expressions.d.mts +46 -2
  126. package/types/engine/note-dates.d.mts +64 -1
  127. package/types/engine/note-frontmatter.d.mts +1 -4
  128. package/types/engine/note-vocabulary.d.mts +82 -6
  129. package/types/engine/page-lists.d.mts +126 -0
  130. package/types/engine/pathnames.d.mts +26 -0
  131. package/types/engine/pdf-build.d.mts +28 -6
  132. package/types/engine/pdf-images.d.mts +52 -0
  133. package/types/engine/pdf-render.d.mts +84 -2
  134. package/types/engine/rank-ladder.d.mts +32 -0
  135. package/types/engine/retired-fields.d.mts +4 -6
  136. package/types/engine/site-build.d.mts +27 -4
  137. package/types/engine/site-config.d.mts +9 -0
  138. package/types/engine/site-index.d.mts +67 -1
  139. package/types/engine/site-preview.d.mts +1 -1
  140. package/types/engine/sql-tables.d.mts +31 -7
  141. package/types/engine/standings.d.mts +21 -3
  142. package/types/engine/system-block.d.mts +37 -14
  143. package/types/engine/web-wikilinks.d.mts +3 -1
  144. package/types/hm3/actors.d.mts +21 -3
  145. package/types/manifest.d.mts +19 -23
  146. package/types/sohl/actors.d.mts +6 -7
  147. package/engine/content-admonitions.mjs +0 -114
  148. package/engine/content-captions.mjs +0 -181
  149. package/engine/content-secrets.mjs +0 -112
  150. package/types/engine/content-admonitions.d.mts +0 -24
  151. package/types/engine/content-captions.d.mts +0 -45
  152. package/types/engine/content-secrets.d.mts +0 -17
@@ -24,6 +24,60 @@ export function buildSiteIndex(entries: readonly SiteEntry[], { foreignIndex, no
24
24
  records?: object[] | undefined;
25
25
  foreignReferences?: Map<string, object> | undefined;
26
26
  }): SiteIndex;
27
+ /**
28
+ * The address keys one page's figures are reachable by — the same two forms
29
+ * {@link buildSiteIndex} assigns the page itself, so a `ref` crossing into it
30
+ * resolves by exactly the address a wikilink would use.
31
+ *
32
+ * @param {string} pkg - The page's own content package.
33
+ * @param {string} type - The note's `type`.
34
+ * @param {string} shortcode - The note's `shortcode`.
35
+ * @returns {Set<string>} The short `type/shortcode` key and the canonical
36
+ * `package-system-type-shortcode` one.
37
+ */
38
+ export function figureIndexKeys(pkg: string, type: string, shortcode: string): Set<string>;
39
+ /**
40
+ * Resolve a `ref` call's cross-note address to the target's own figures and
41
+ * its own address — the `figures.note` a `ref` helper's render context reads,
42
+ * built once and threaded into every page.
43
+ *
44
+ * Reads the written address exactly as a wikilink does
45
+ * ({@link readQualifier}, {@link expandAddress}), so a `ref` crossing into
46
+ * another note and a `[[…]]` link to the same note agree on what resolves and
47
+ * what does not: a target naming no known type or no fetched package's index
48
+ * is unresolved for both, and one address names one note for both.
49
+ *
50
+ * @param {string} target - The address as written, anchor already removed —
51
+ * {@link module:engine/wikilink-syntax.ParsedWikilink.target}.
52
+ * @param {object} options
53
+ * @param {Set<string>} options.contentTypes - Every type this build reads as
54
+ * an address qualifier.
55
+ * @param {Set<string>} options.packages - Every package an address may name.
56
+ * @param {Set<string>} options.noIndexPackages - Packages declared
57
+ * `contentIndex: false`.
58
+ * @param {string} options.contentPackage - The package a target with no
59
+ * package segment defaults to.
60
+ * @param {Map<string, {url: string}>} options.siteIndexMap - {@link SiteIndex}'s
61
+ * own `index`, read for the target's existence and its URL.
62
+ * @param {Map<string, Map<string, object>>} options.figuresByAddress - Every
63
+ * note's figures, by id, keyed as {@link figureIndexKeys} keys the note.
64
+ * @returns {{url: string, figures: Map<string, object>}|undefined} The
65
+ * target's own address and figures, or `undefined` when the address names
66
+ * no note this build resolves.
67
+ */
68
+ export function resolveCrossNoteFigures(target: string, { contentTypes, packages, noIndexPackages, contentPackage, siteIndexMap, figuresByAddress }: {
69
+ contentTypes: Set<string>;
70
+ packages: Set<string>;
71
+ noIndexPackages: Set<string>;
72
+ contentPackage: string;
73
+ siteIndexMap: Map<string, {
74
+ url: string;
75
+ }>;
76
+ figuresByAddress: Map<string, Map<string, object>>;
77
+ }): {
78
+ url: string;
79
+ figures: Map<string, object>;
80
+ } | undefined;
27
81
  /**
28
82
  * The per-page context a wikilink resolver takes.
29
83
  *
@@ -106,6 +160,12 @@ export type SiteEntry = {
106
160
  * The page's published address.
107
161
  */
108
162
  url: string;
163
+ /**
164
+ * The note's markdown body, for its anchors. A
165
+ * content entry always carries one; absent, the
166
+ * page is indexed as declaring none.
167
+ */
168
+ body?: string | undefined;
109
169
  };
110
170
  /**
111
171
  * The resolved index and everything a wikilink resolver reads beside it.
@@ -114,12 +174,18 @@ export type SiteIndex = {
114
174
  /**
115
175
  * Address → page. `draft` says the page
116
176
  * carries the `draft` tag, which marks a
117
- * link *into* it.
177
+ * link *into* it. `anchors` names the
178
+ * `{#slug}` sections it declares — a
179
+ * `Set` for a local page, or the
180
+ * `{slug: uuid}` map a foreign manifest
181
+ * publishes — and is absent where
182
+ * neither build recorded one.
118
183
  */
119
184
  index: Map<string, {
120
185
  url: string;
121
186
  name?: string;
122
187
  draft?: boolean;
188
+ anchors?: Set<string> | Record<string, string>;
123
189
  }>;
124
190
  /**
125
191
  * Short addresses claimed by two
@@ -24,7 +24,7 @@ export function prepareSitePreview({ config }?: {
24
24
  frontmatter?: undefined;
25
25
  } | {
26
26
  ok: boolean;
27
- markdown: any;
27
+ markdown: string;
28
28
  frontmatter: any;
29
29
  findings: never[];
30
30
  }>;
@@ -142,6 +142,10 @@ export function renderSqlTable(result: {
142
142
  * @param {object} [opts]
143
143
  * @param {(ref: string) => boolean} [opts.linkable] - Passed to
144
144
  * {@link renderSqlTable}.
145
+ * @param {object[]} [opts.records] - Content-index records, narrowed to the
146
+ * audience this surface publishes to. Given them, the result also carries
147
+ * `pageLists`, so one prepared object answers every corpus-reading directive
148
+ * a body can hold and a caller looks results up in one place.
145
149
  * **Keyed by note, then by the directive's ordinal within it** — not by its
146
150
  * line. The passes do not agree on what a body is: `walkMarkdownTree` trims it,
147
151
  * while the link checker strips the frontmatter fence and leaves the newlines
@@ -154,8 +158,9 @@ export function renderSqlTable(result: {
154
158
  export function prepareSqlTables(db: object, sources: Array<{
155
159
  source: string;
156
160
  markdown: string;
157
- }>, { linkable }?: {
161
+ }>, { linkable, records }?: {
158
162
  linkable?: ((ref: string) => boolean) | undefined;
163
+ records?: object[] | undefined;
159
164
  }): Promise<Map<string, object[]>>;
160
165
  /** Evaluate scalar SQL calls before synchronous Markdown renderers run. */
161
166
  export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<any, any>>;
@@ -167,10 +172,14 @@ export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<
167
172
  * shared index
168
173
  * describes, where N passes each derive the corpus their own way.
169
174
  *
170
- * **Nothing is opened for a tree with no `sql` directive.** The corpus is still
171
- * written entirely in the retiring language, so until a table is converted this
172
- * costs one walk and no database at all — which is what lets every pass call it
173
- * unconditionally.
175
+ * **Every corpus-reading directive is answered here**, which is why one call
176
+ * precedes every pass: the `sql` tables, the inline scalar queries, and the
177
+ * page lists, whose results ride on the same object as `pageLists`.
178
+ *
179
+ * **Nothing is opened for a tree that asks no query.** A tree with no `sql`
180
+ * directive costs one walk and no database at all, and so does one whose only
181
+ * directive is a page list — a page list is a filter over the records. That is
182
+ * what lets every pass call this unconditionally.
174
183
  *
175
184
  * @param {string} contentBase - Root of the content tree.
176
185
  * @param {object} [opts]
@@ -180,8 +189,9 @@ export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<
180
189
  * A command that also builds a link index holds them already, and deriving
181
190
  * them twice is the duplicated-corpus failure this closes.
182
191
  * @param {"all"|"public"} [opts.audience] - Whether to exclude GM notes.
183
- * @returns {Promise<Map<string, object[]>|undefined>} Results by note path, or
184
- * nothing when the tree has no such directive.
192
+ * @returns {Promise<Map<string, object[]>|undefined>} Results by note path,
193
+ * carrying `inline` and `pageLists` beside them, or nothing when the tree
194
+ * holds no such directive.
185
195
  */
186
196
  export function prepareTreeSqlTables(contentBase: string, { config, skipDirectories, records, audience }?: {
187
197
  config?: object | undefined;
@@ -200,3 +210,17 @@ export const RENDER_ALIASES: Readonly<{
200
210
  ref: "_ref";
201
211
  section: "_section";
202
212
  }>;
213
+ /**
214
+ * The attributes an SQL fence takes, with what each one is for.
215
+ *
216
+ * **This is the one list.** The fence is validated against it and the authoring
217
+ * document is checked against it, so an attribute added here is one the document
218
+ * must describe, and anything else an author writes is reported by name.
219
+ *
220
+ * @type {Readonly<Record<string, Readonly<{value: string, default: string, summary: string}>>>}
221
+ */
222
+ export const SQL_FENCE_ATTRIBUTES: Readonly<Record<string, Readonly<{
223
+ value: string;
224
+ default: string;
225
+ summary: string;
226
+ }>>>;
@@ -37,6 +37,10 @@ export function readStandings(value: unknown): {
37
37
  * than by its position in the list: a body may write its rungs in any order,
38
38
  * and several start at zero.
39
39
  *
40
+ * One title per level, which is what the rung check holds a ladder to: two
41
+ * rungs claiming one level is a finding on the second, so a ladder that reaches
42
+ * here carries at most one title for any number a member's `rank` can hold.
43
+ *
40
44
  * @param {unknown} ranks - An affiliation's `data.governance.ranks`.
41
45
  * @returns {Map<number, string>} Level → the title that level is called.
42
46
  */
@@ -66,6 +70,18 @@ export function officeRoster(offices: unknown): Map<string, string>;
66
70
  * @returns {string} The anchor, or `""` where the name carries nothing.
67
71
  */
68
72
  export function officeAnchor(office: string): string;
73
+ /**
74
+ * The anchor a rung's row is addressed by.
75
+ *
76
+ * The ladder's parallel to {@link officeAnchor}, prefixed apart from it so a
77
+ * body naming an office the same word as one of its ranks still gets two
78
+ * destinations rather than one row answering for both.
79
+ *
80
+ * @param {string} title - The rung's title, as the body wrote it.
81
+ * @returns {string} The anchor, or `""` where the title carries nothing
82
+ * URL-safe.
83
+ */
84
+ export function rankAnchor(title: string): string;
69
85
  /**
70
86
  * What an affiliation lends to a reference to it.
71
87
  *
@@ -106,12 +122,14 @@ export function standingPhrase(standing: object, body: string, digest?: {
106
122
  /**
107
123
  * Validate a being's memberships and the standing it holds in each.
108
124
  *
109
- * Four questions, and the last two are what keying the entry by its body buys:
125
+ * Five questions, and the last three are what keying the entry by its body
126
+ * buys:
110
127
  *
111
128
  * 1. the value is a map keyed by Address (or the list this window still takes);
112
129
  * 2. every key names an affiliation that resolves, once;
113
- * 3. a `rank` is a level the named body's `governance.ranks` declares;
114
- * 4. an `office` is a key of that body's `governance.offices`.
130
+ * 3. the entry states a `rank`;
131
+ * 4. a `rank` is a level the named body's `governance.ranks` declares;
132
+ * 5. an `office` is a key of that body's `governance.offices`.
115
133
  *
116
134
  * Both of the last two are read from the named body's own frontmatter as the
117
135
  * lint runs, so a renamed rung or a renamed post fails on every being pointing
@@ -79,23 +79,48 @@ export function blockField(fm: object, block: string, key: string, defaultValue?
79
79
  */
80
80
  export function sharedProperty(fm: object, source: string, defaultValue?: any): any;
81
81
  /**
82
- * A property a system block may override, else the shared top-level one.
82
+ * A property a system block declares, else the default.
83
83
  *
84
- * This is what gives `pack`, `effects`, `flags` and `img` their per-system form
85
- * without inventing a mechanism for each: a note that wants one value for both
86
- * systems says it once at the top, and a note that needs them to differ says so
87
- * in the block that differs.
84
+ * This is what gives `effects` and `flags` their per-system form without
85
+ * inventing a mechanism for each: a note states them inside the block whose
86
+ * document carries them, which is the only position the format accepts for
87
+ * either. A note needing one value for both systems writes it in both blocks —
88
+ * they are properties of a document, and there are two documents.
88
89
  *
89
90
  * @param {object} fm - The note's frontmatter.
90
91
  * @param {string} block - The block key.
91
92
  * @param {string} key - The property.
92
- * @param {any} [defaultValue] - Returned when neither declares it.
93
+ * @param {any} [defaultValue] - Returned when the block does not declare it.
93
94
  * @returns {any} The value.
94
95
  */
95
96
  export function blockProperty(fm: object, block: string, key: string, defaultValue?: any): any;
96
97
  /**
97
- * A system override followed by a shared `data:` value. The top-level value
98
- * remains readable while content repositories move their authored fields.
98
+ * A system override followed by a shared `data:` value followed by the
99
+ * retiring top-level spelling `data:` gathered it off, reporting which one
100
+ * answered.
101
+ *
102
+ * {@link module:engine/field-spec.resolveFieldValue}'s counterpart for a
103
+ * universal `data:` key — `pack` and `packFolder`, declared for every note
104
+ * type rather than per type, so neither is a `FieldSpec` that resolver takes.
105
+ * The order is the same shape: a system's own override, then the shared
106
+ * `data:` position, then the bare top-level key a tree authored before
107
+ * `data:` existed. The top-level value stays readable while a tree still on
108
+ * it moves its authored fields.
109
+ *
110
+ * @param {object} fm - The note's frontmatter.
111
+ * @param {string} block - The system block key.
112
+ * @param {string} key - The shared property.
113
+ * @param {any} [defaultValue] - Returned when no position declares it.
114
+ * @returns {{value: any, from: FieldSource}} The value, and where it came
115
+ * from.
116
+ */
117
+ export function resolveDataProperty(fm: object, block: string, key: string, defaultValue?: any): {
118
+ value: any;
119
+ from: FieldSource;
120
+ };
121
+ /**
122
+ * {@link resolveDataProperty}'s value alone, for a caller that only compiles
123
+ * a document and has no use for which position answered.
99
124
  *
100
125
  * @param {object} fm - The note's frontmatter.
101
126
  * @param {string} block - The system block key.
@@ -128,12 +153,10 @@ export function legacyKeyOf(field: {
128
153
  /**
129
154
  * The bare top-level key a `data:`-sourced field is being swept off — step 3b.
130
155
  *
131
- * `data:` did not invent the facts it holds; it *gathered* them, out of
132
- * the note's open top level where each was a sibling of `img` and `shortcode`.
133
- * So the retiring spelling of `data.portrait` is not a second declaration
134
- * anyone has to write — it is `portrait`, mechanically, and the same holds for
135
- * every other key that move relocated. Deriving it is what keeps the two
136
- * spellings of one field from disagreeing the way two declarations would.
156
+ * The retiring spelling of `data.portrait` is not a second declaration anyone
157
+ * has to write — it is `portrait`, mechanically, and the same holds for every
158
+ * other key the container gathered. Deriving it is what keeps the two spellings
159
+ * of one field from disagreeing the way two declarations would.
137
160
  *
138
161
  * **Only a `data.` source has one.** `protection.blunt` and `impact.die` are
139
162
  * paths into containers a note has always written at the top level; they were
@@ -97,7 +97,9 @@ export function frontmatterWikilinks(fm: unknown): Array<{
97
97
  * resolves against. `src` is the page's display
98
98
  * path and `file` the source file a diagnostic should name — absent, `src`
99
99
  * stands in. `resolved`, when supplied, is the array every resolved
100
- * target's index entry is appended to.
100
+ * target's index entry is appended to. `anchors` is the `{#slug}` set
101
+ * *this* page declares, which a `[[#slug]]` self-link is checked against;
102
+ * absent, a self-link is not checked, exactly as before this existed.
101
103
  * @returns {string} The body with embeds and wikilinks rewritten.
102
104
  */
103
105
  export function resolveWebWikilinks(body: string, ctx: object): string;
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Native HM3 facts shared by its actor subtypes.
3
+ *
4
+ * Species is native text. The note's lore Address in `data.species` is
5
+ * independent and supplies no native value.
6
+ *
7
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
8
+ */
9
+ export const ACTOR_FIELDS: readonly import("../engine/field-spec.mjs").FieldSpec[];
10
+ /**
11
+ * The two `data:` facts HM3 declares on a `character` and not on a `creature`.
12
+ *
13
+ * Both name their shared source and their legacy in-block key, for the reason
14
+ * {@link ACTOR_FIELDS} does.
15
+ *
16
+ * @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
17
+ */
18
+ export const CHARACTER_FIELDS: readonly import("../engine/field-spec.mjs").FieldSpec[];
1
19
  /**
2
20
  * HM3's Actor compile pass.
3
21
  *
@@ -27,9 +45,9 @@ export class Hm3Actors extends SystemActorCompiler {
27
45
  /**
28
46
  * Build every embedded item an HM3 actor carries, from `hm3.items`.
29
47
  *
30
- * One list, not two: HM3 keeps a character's abilities in `system.abilities`
31
- * rather than as embedded documents, so there is no attributes map to
32
- * expand the way SoHL's pass expands `sohl.attributes`.
48
+ * HM3 keeps a character's abilities in `system.abilities` rather than as
49
+ * embedded documents, so an ability reaches the document through the
50
+ * `hm3.system` passthrough and never through this list.
33
51
  *
34
52
  * @param {Map<string, object>} itemsMap - The predefined items, by address.
35
53
  * @param {string} actorId - The owning actor's id, seeding embedded ids.
@@ -267,29 +267,25 @@ export const ARTIFACTS: readonly string[];
267
267
  */
268
268
  export const HOMEPAGE_ORIGIN: string;
269
269
  /**
270
- * Relationship keys that direct the **build**, rather than describe the
271
- * package.
272
- *
273
- * `relationships` is the one manifest block with a second reader.
274
- * `@heroiclands/package-build` consumes it too, and v1.8.0 added
275
- * `itemCatalog: true` as an opt-in on a declared dependency: it selects that
276
- * package's Item packs as a resolution
277
- * source for the actors pass. That is an instruction to the build, not a fact
278
- * about the shipped package — Foundry's relationship schema does not define
279
- * it, and someone reading a published manifest cannot tell a build directive
280
- * from a declaration about what the package needs.
281
- *
282
- * So the block is filtered rather than copied whole. The rule is the
283
- * distinction, not the name: a key listed here answers *how is this built?*,
284
- * and every key that survives answers *what does this package depend on?*.
285
- * `itemCatalog` is the first build-time key to land on a relationship and is
286
- * unlikely to be the last.
287
- *
288
- * A list is enough, and needs no prefix agreed between the two packages,
289
- * because the input is already closed: package-build normalises a relationship
290
- * to `id`, `type`, `manifest`, `compatibility` and its own build keys, and
291
- * rejects anything else at configuration time. A key that reaches here is one
292
- * the toolchain itself put there.
270
+ * The relationship keys Foundry's own schema names.
271
+ *
272
+ * `RelatedPackage` declares exactly these (`common/packages/base-package.mjs`),
273
+ * so a key outside them is one the build put there for itself.
274
+ *
275
+ * @type {readonly string[]}
276
+ */
277
+ export const FOUNDRY_RELATIONSHIP_KEYS: readonly string[];
278
+ /**
279
+ * The relationship keys the published manifest drops.
280
+ *
281
+ * Derived rather than listed: every key this package accepts on a relationship
282
+ * that Foundry's schema does not name is one the build reads for itself, so a
283
+ * key added to the configuration is dropped from the manifest without a second
284
+ * list being kept in step.
285
+ *
286
+ * The filter stays a denylist for the reason the pass-through below gives — a
287
+ * key Foundry adds later reaches the manifest unread rather than being stripped
288
+ * by an allowlist that has not heard of it.
293
289
  *
294
290
  * @type {readonly string[]}
295
291
  */
@@ -2,9 +2,8 @@
2
2
  * SoHL's Actor compile pass.
3
3
  *
4
4
  * Declares SoHL's note-type → document-subtype map and builds a `being` note's
5
- * document: the body structure and its movement profiles, the embedded
6
- * attribute and item documents the frontmatter names, and the `system` block.
7
- * Everything else is {@link module:engine/actor-compiler}'s.
5
+ * document: the embedded item documents the frontmatter names, and the `system`
6
+ * block. Everything else is {@link module:engine/actor-compiler}'s.
8
7
  */
9
8
  export class Actors extends SystemActorCompiler {
10
9
  /**
@@ -17,8 +16,8 @@ export class Actors extends SystemActorCompiler {
17
16
  */
18
17
  static documentSubtypes: import("../engine/document-subtypes.mjs").DocumentSubtypeMap;
19
18
  /**
20
- * Build all embedded items for an actor: one per `sohl.attributes`
21
- * entry plus one per `sohl.items` entry. `sohl.skills` is ignored.
19
+ * Build all embedded items for an actor: one per `sohl.items` entry, plus
20
+ * one per body the being belongs to.
22
21
  */
23
22
  buildEmbeddedItems(itemsMap: any, actorId: any, fm: any, ctx: any): object[];
24
23
  /**
@@ -57,8 +56,8 @@ export class Actors extends SystemActorCompiler {
57
56
  *
58
57
  * **The scores used are the ones just written.** `SkillLogic` resolves
59
58
  * `attr.<code>` to an attribute's *effective* score, after active effects;
60
- * all this pass has is the `scoreBase` it set from `sohl.attributes`. For a
61
- * compiled being carrying no attribute-altering effects the two agree,
59
+ * all this pass has is the `scoreBase` on each embedded attribute item. For
60
+ * a compiled being carrying no attribute-altering effects the two agree,
62
61
  * which is every being in content today. One that did carry such an effect
63
62
  * would bake a Skill Base its client then disagrees with — that is the
64
63
  * limit of doing this at build time, and the point to revisit if it bites.
@@ -1,114 +0,0 @@
1
- /* SPDX-License-Identifier: GPL-3.0-or-later */
2
-
3
- import MarkdownIt from "markdown-it";
4
- import footnotePlugin from "markdown-it-footnote";
5
- import deflistPlugin from "markdown-it-deflist";
6
- import { parseExtensionAttributes } from "./extension-attributes.mjs";
7
-
8
- const parser = new MarkdownIt({ html: true }).use(footnotePlugin).use(deflistPlugin);
9
- const OPEN = /^:::(info|warn)(?:\s+(.*))?\s*$/;
10
- const CLOSE = /^:::\s*$/;
11
- const STYLES = Object.freeze({
12
- info: { label: "Info", icon: "ℹ", color: "#2f6f9f", background: "#eef6fb" },
13
- warn: { label: "Warning", icon: "⚠", color: "#9a6700", background: "#fff5db" },
14
- });
15
-
16
- /** Find inline info and warning blocks without interpreting code examples. */
17
- export function scanAdmonitions(source) {
18
- const lines = String(source ?? "").split("\n");
19
- const blocks = [];
20
- const errors = [];
21
- let opening = null;
22
- let codeFence = null;
23
- for (let i = 0; i < lines.length; i++) {
24
- const line = lines[i];
25
- const fence = /^ {0,3}(`{3,}|~{3,})/.exec(line);
26
- if (codeFence) {
27
- if (fence && fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length)
28
- codeFence = null;
29
- continue;
30
- }
31
- if (fence) {
32
- codeFence = fence[1];
33
- continue;
34
- }
35
- const open = OPEN.exec(line);
36
- if (open) {
37
- if (opening) {
38
- errors.push({
39
- line: i + 1,
40
- column: 1,
41
- message: "nested info and warning blocks are not supported",
42
- });
43
- continue;
44
- }
45
- const raw = open[2]?.trim() ?? "";
46
- let id = "";
47
- if (raw) {
48
- if (!/^\{[^}\n]*\}$/.test(raw))
49
- errors.push({
50
- line: i + 1,
51
- column: 1,
52
- message: "block attributes need {#id} syntax",
53
- });
54
- else {
55
- const attrs = parseExtensionAttributes(raw.slice(1, -1));
56
- id = attrs.id;
57
- for (const message of attrs.problems)
58
- errors.push({ line: i + 1, column: 1, message });
59
- if (attrs.classes.length || Object.keys(attrs.values).length)
60
- errors.push({
61
- line: i + 1,
62
- column: 1,
63
- message: "info and warn accept only an id",
64
- });
65
- }
66
- }
67
- opening = { start: i, kind: open[1], id };
68
- continue;
69
- }
70
- if (CLOSE.test(line) && opening) {
71
- const body = lines
72
- .slice(opening.start + 1, i)
73
- .join("\n")
74
- .trim();
75
- if (!body)
76
- errors.push({
77
- line: opening.start + 1,
78
- column: 1,
79
- message: `${opening.kind} block is empty`,
80
- });
81
- blocks.push({ ...opening, end: i, body });
82
- opening = null;
83
- }
84
- }
85
- if (opening)
86
- errors.push({
87
- line: opening.start + 1,
88
- column: 1,
89
- message: `${opening.kind} block needs a closing ::: line`,
90
- });
91
- return { blocks, errors };
92
- }
93
-
94
- /** Render info and warning blocks as styled, labelled HTML. */
95
- export function renderAdmonitions(source, renderMarkdown = parser.render.bind(parser)) {
96
- const { blocks, errors } = scanAdmonitions(source);
97
- if (errors.length) return { markdown: source, errors };
98
- const lines = String(source ?? "").split("\n");
99
- const output = [];
100
- let cursor = 0;
101
- for (const block of blocks) {
102
- const style = STYLES[block.kind];
103
- output.push(...lines.slice(cursor, block.start));
104
- output.push(
105
- `<aside${block.id ? ` id="${block.id}"` : ""} class="sohl-admonition sohl-admonition-${block.kind}" style="border-left: 3px solid ${style.color}; background: ${style.background}; padding: 0.5em 0.8em; margin: 0.75em 0;">`,
106
- `<p><strong><span aria-hidden="true">${style.icon}</span> ${style.label}</strong></p>`,
107
- renderMarkdown(block.body).trim(),
108
- "</aside>",
109
- );
110
- cursor = block.end + 1;
111
- }
112
- output.push(...lines.slice(cursor));
113
- return { markdown: output.join("\n"), errors: [] };
114
- }