@heroiclands/package-build 17.1.0 → 18.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.
Files changed (153) hide show
  1. package/CHANGELOG.md +1773 -0
  2. package/CONTENT.md +288 -72
  3. package/README.md +1 -1
  4. package/bin/content-build.mjs +218 -137
  5. package/bin/package-build.mjs +43 -0
  6. package/content-config.mjs +62 -67
  7. package/docs/content-format.md +768 -58
  8. package/engine/actor-compiler.mjs +586 -0
  9. package/engine/address-charset.mjs +11 -8
  10. package/engine/address-diff.mjs +266 -22
  11. package/engine/anchored-sections.mjs +83 -0
  12. package/engine/anchors.mjs +83 -0
  13. package/engine/base-compiler.mjs +148 -18
  14. package/engine/bundle-notes.mjs +276 -0
  15. package/engine/bundles.mjs +307 -0
  16. package/engine/code-fences.mjs +103 -0
  17. package/engine/compile-corpus.mjs +89 -0
  18. package/engine/content-address.mjs +267 -5
  19. package/engine/content-format-check.mjs +54 -11
  20. package/engine/content-format.mjs +37 -5
  21. package/engine/content-index.mjs +394 -111
  22. package/engine/content-links.mjs +185 -77
  23. package/engine/content-lint.mjs +232 -14
  24. package/engine/content-package.mjs +2 -1
  25. package/engine/content-tables.mjs +99 -3
  26. package/engine/document-subtypes.mjs +49 -6
  27. package/engine/field-reference.mjs +1 -1
  28. package/engine/field-spec.mjs +56 -6
  29. package/engine/folder-notes.mjs +470 -0
  30. package/engine/foreign-catalog.mjs +195 -4
  31. package/engine/{manifest-emit.mjs → foundry-entries.mjs} +78 -81
  32. package/engine/frontmatter-lint.mjs +245 -19
  33. package/engine/frontmatter.mjs +88 -8
  34. package/engine/generate.mjs +304 -13
  35. package/engine/helpers.mjs +293 -64
  36. package/engine/ids.mjs +137 -2
  37. package/engine/index-records.mjs +126 -0
  38. package/engine/index.mjs +23 -5
  39. package/engine/item-compiler.mjs +349 -0
  40. package/engine/item-docs.mjs +7 -1
  41. package/engine/item-registry.mjs +6 -0
  42. package/engine/journals.mjs +93 -27
  43. package/engine/macros.mjs +4 -2
  44. package/engine/metadata-index.mjs +495 -0
  45. package/engine/note-claims.mjs +146 -25
  46. package/engine/note-ids.mjs +112 -0
  47. package/engine/note-renames.mjs +134 -0
  48. package/engine/note-schemas.mjs +25 -0
  49. package/engine/note-vocabulary.mjs +110 -27
  50. package/engine/pack-config.mjs +4 -0
  51. package/engine/pack-router.mjs +23 -0
  52. package/engine/retired-fields.mjs +137 -3
  53. package/engine/scenes.mjs +66 -34
  54. package/engine/schema-check.mjs +58 -17
  55. package/engine/site-build.mjs +71 -12
  56. package/engine/site-index.mjs +20 -7
  57. package/engine/sql-tables.mjs +485 -0
  58. package/engine/subtype-registry.mjs +102 -0
  59. package/engine/system-block.mjs +96 -12
  60. package/engine/systems.mjs +201 -0
  61. package/engine/web-wikilinks.mjs +50 -20
  62. package/engine/wikilink-syntax.mjs +17 -8
  63. package/engine/wikilinks.mjs +124 -51
  64. package/engine/yaml-lint.mjs +282 -0
  65. package/hm3/actors.mjs +352 -0
  66. package/hm3/default-item-art.mjs +75 -0
  67. package/hm3/document-subtypes.mjs +134 -0
  68. package/hm3/index.mjs +56 -0
  69. package/hm3/item-builders.mjs +84 -0
  70. package/hm3/item-fields.mjs +180 -0
  71. package/hm3/items.mjs +96 -0
  72. package/hm3/template-priority.mjs +85 -0
  73. package/labels.mjs +191 -0
  74. package/manifest.mjs +43 -2
  75. package/package.json +19 -4
  76. package/release.mjs +62 -7
  77. package/sohl/actors.mjs +33 -487
  78. package/sohl/being-info.mjs +16 -7
  79. package/sohl/default-item-art.mjs +14 -3
  80. package/sohl/document-subtypes.mjs +16 -10
  81. package/sohl/item-builders.mjs +14 -5
  82. package/sohl/item-fields.mjs +68 -7
  83. package/sohl/items.mjs +44 -258
  84. package/sohl/note-schemas.mjs +2 -2
  85. package/types/content-config.d.mts +21 -42
  86. package/types/engine/actor-compiler.d.mts +204 -0
  87. package/types/engine/address-charset.d.mts +11 -8
  88. package/types/engine/address-diff.d.mts +53 -5
  89. package/types/engine/anchored-sections.d.mts +21 -0
  90. package/types/engine/anchors.d.mts +20 -0
  91. package/types/engine/base-compiler.d.mts +17 -17
  92. package/types/engine/bundle-notes.d.mts +173 -0
  93. package/types/engine/bundles.d.mts +60 -0
  94. package/types/engine/code-fences.d.mts +43 -0
  95. package/types/engine/compile-corpus.d.mts +32 -0
  96. package/types/engine/content-address.d.mts +205 -5
  97. package/types/engine/content-format-check.d.mts +6 -2
  98. package/types/engine/content-format.d.mts +57 -1
  99. package/types/engine/content-index.d.mts +64 -80
  100. package/types/engine/content-links.d.mts +52 -7
  101. package/types/engine/content-lint.d.mts +10 -1
  102. package/types/engine/content-package.d.mts +2 -1
  103. package/types/engine/content-tables.d.mts +21 -39
  104. package/types/engine/document-subtypes.d.mts +37 -3
  105. package/types/engine/field-spec.d.mts +76 -5
  106. package/types/engine/folder-notes.d.mts +159 -0
  107. package/types/engine/foreign-catalog.d.mts +53 -0
  108. package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +16 -38
  109. package/types/engine/frontmatter-lint.d.mts +10 -2
  110. package/types/engine/frontmatter.d.mts +64 -0
  111. package/types/engine/generate.d.mts +38 -0
  112. package/types/engine/helpers.d.mts +94 -30
  113. package/types/engine/ids.d.mts +96 -0
  114. package/types/engine/index-records.d.mts +68 -0
  115. package/types/engine/index.d.mts +9 -3
  116. package/types/engine/item-compiler.d.mts +131 -0
  117. package/types/engine/journals.d.mts +47 -9
  118. package/types/engine/metadata-index.d.mts +226 -0
  119. package/types/engine/note-claims.d.mts +57 -11
  120. package/types/engine/note-ids.d.mts +38 -0
  121. package/types/engine/note-renames.d.mts +102 -0
  122. package/types/engine/note-vocabulary.d.mts +44 -8
  123. package/types/engine/retired-fields.d.mts +75 -0
  124. package/types/engine/scenes.d.mts +3 -2
  125. package/types/engine/schema-check.d.mts +25 -4
  126. package/types/engine/site-build.d.mts +4 -4
  127. package/types/engine/site-index.d.mts +1 -1
  128. package/types/engine/sql-tables.d.mts +185 -0
  129. package/types/engine/subtype-registry.d.mts +49 -0
  130. package/types/engine/system-block.d.mts +40 -1
  131. package/types/engine/systems.d.mts +106 -0
  132. package/types/engine/web-wikilinks.d.mts +4 -2
  133. package/types/engine/wikilink-syntax.d.mts +10 -3
  134. package/types/engine/wikilinks.d.mts +41 -13
  135. package/types/engine/yaml-lint.d.mts +107 -0
  136. package/types/hm3/actors.d.mts +48 -0
  137. package/types/hm3/default-item-art.d.mts +42 -0
  138. package/types/hm3/document-subtypes.d.mts +24 -0
  139. package/types/hm3/index.d.mts +7 -0
  140. package/types/hm3/item-builders.d.mts +11 -0
  141. package/types/hm3/item-fields.d.mts +12 -0
  142. package/types/hm3/items.d.mts +23 -0
  143. package/types/hm3/template-priority.d.mts +21 -0
  144. package/types/manifest.d.mts +22 -1
  145. package/types/release.d.mts +9 -4
  146. package/types/sohl/actors.d.mts +7 -74
  147. package/types/sohl/being-info.d.mts +8 -27
  148. package/types/sohl/default-item-art.d.mts +5 -3
  149. package/types/sohl/items.d.mts +17 -32
  150. package/engine/foreign-manifests.mjs +0 -126
  151. package/engine/kb-manifest.mjs +0 -490
  152. package/types/engine/foreign-manifests.d.mts +0 -43
  153. package/types/engine/kb-manifest.d.mts +0 -241
@@ -7,7 +7,10 @@
7
7
  * pack from appearing to answer for any note.
8
8
  *
9
9
  * @param {string} docType - The Foundry document type a pack holds.
10
- * @param {ClaimSources} [sources] - What to answer from. Defaults to the
10
+ * @param {ClaimSources} [sources] - What to answer from.
11
+ * @param {object} [opts] - Options.
12
+ * @param {readonly object[]} [opts.records] - The corpus, derived once by the
13
+ * compile and handed in — required, for the reason above (#243). Defaults to the
11
14
  * configured registries and the systems this toolchain ships.
12
15
  * @returns {ReadonlySet<string>} The note types such a pass would claim.
13
16
  */
@@ -19,9 +22,21 @@ export function noteTypesClaimedBy(docType: string, sources?: ClaimSources): Rea
19
22
  * is claimed — which is what keeps a type deliberately unmapped for one system,
20
23
  * and claimed for another, silent (#79).
21
24
  *
25
+ * **A prebuilt pack claims nothing.** Its per-document JSON is checked in
26
+ * rather than compiled, so it has no pass and no note is routed into one —
27
+ * which `content-config.mjs` already states by refusing `default: true`
28
+ * alongside `prebuilt`. Counting it would tell an author their note is claimed
29
+ * by a pack that will never look at it. Before #259 the point could not arise:
30
+ * the only prebuilt pack in the wild is `harn-adventures`'s Adventure pack, and
31
+ * no compiler was registered for that document type, so the row answered for
32
+ * nothing whatever it was asked. Now one is.
33
+ *
22
34
  * @param {object} [config] - The resolved build configuration. Defaults to this
23
35
  * repository's.
24
36
  * @param {ClaimSources} [sources] - What to answer from.
37
+ * @param {object} [opts] - Options.
38
+ * @param {readonly object[]} [opts.records] - The corpus, derived once by the
39
+ * compile and handed in — required, for the reason above (#243).
25
40
  * @returns {ReadonlySet<string>} The claimed note types.
26
41
  */
27
42
  export function claimedNoteTypes(config?: object, sources?: ClaimSources): ReadonlySet<string>;
@@ -35,6 +50,9 @@ export function claimedNoteTypes(config?: object, sources?: ClaimSources): Reado
35
50
  * declare on top.
36
51
  *
37
52
  * @param {ClaimSources} [sources] - What to answer from.
53
+ * @param {object} [opts] - Options.
54
+ * @param {readonly object[]} [opts.records] - The corpus, derived once by the
55
+ * compile and handed in — required, for the reason above (#243).
38
56
  * @returns {ReadonlySet<string>} The vocabulary.
39
57
  */
40
58
  export function noteTypeVocabulary(sources?: ClaimSources): ReadonlySet<string>;
@@ -51,10 +69,15 @@ export function noteTypeVocabulary(sources?: ClaimSources): ReadonlySet<string>;
51
69
  * @param {object} [config] - The resolved build configuration. Defaults to this
52
70
  * repository's.
53
71
  * @param {ClaimSources} [sources] - What to answer from.
72
+ * @param {object} [opts] - Options.
73
+ * @param {readonly object[]} [opts.records] - The corpus, derived once by the
74
+ * compile and handed in — required, for the reason above (#243).
54
75
  * @returns {Array<{file: string, line?: number, column?: number,
55
76
  * severity: "error", message: string, type: string}>} One finding per note.
56
77
  */
57
- export function unclaimedNoteFindings(config?: object, sources?: ClaimSources): Array<{
78
+ export function unclaimedNoteFindings(config?: object, sources?: ClaimSources, { records }?: {
79
+ records?: readonly object[] | undefined;
80
+ }): Array<{
58
81
  file: string;
59
82
  line?: number;
60
83
  column?: number;
@@ -74,19 +97,42 @@ export function unclaimedNoteFindings(config?: object, sources?: ClaimSources):
74
97
  */
75
98
  export const NEVER_PACKED_TYPES: ReadonlySet<string>;
76
99
  /**
77
- * The note-type document-subtype maps this toolchain ships.
100
+ * Content types the specification states and this toolchain does not yet
101
+ * compile.
102
+ *
103
+ * **Stated, never inferred, and that distinction is the whole point.** An
104
+ * unimplemented type and a type somebody forgot to route look identical from
105
+ * the outside: both are documented, both validate, and neither reaches a pass.
106
+ * The only thing separating them is intent, so intent is written down here.
78
107
  *
79
- * One today. `hm3/` is #139, and when it lands its map joins this list rather
80
- * than the claim table below growing a second copy of the same fact.
108
+ * Inferring it "declared, but absent from the configured vocabulary" reads
109
+ * correctly and is worthless, because the configured vocabulary is *derived
110
+ * from the routing*. Take a type's route away and it leaves the vocabulary too,
111
+ * so the inference excuses precisely the mistake it was meant to catch. That is
112
+ * not hypothetical: it is #241, where `place`, `lore` and `scenario` were
113
+ * declared, validated and unrouted, and every gate reported success until a
114
+ * downstream repository failed on 450 notes.
81
115
  *
82
- * `engine/` importing from `sohl/` is the arrangement `generate.mjs` already
83
- * has its `COMPILERS` table names the SoHL compilers by class — and for the
84
- * same reason: the engine owns the *mechanism* that asks each system what it
85
- * compiles, and the systems own the answers.
116
+ * A type leaves this set when it is implemented, the way `bundle` did in #259.
117
+ * The membership is asserted, so it cannot be forgotten in either direction.
86
118
  *
87
- * @type {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]}
119
+ * @type {ReadonlySet<string>}
120
+ */
121
+ export const UNIMPLEMENTED_TYPES: ReadonlySet<string>;
122
+ /**
123
+ * Note types that reach a pack by a route **other than the pack router**.
124
+ *
125
+ * A folder is the only one, and it is not unclaimed: it compiles to a real
126
+ * `Folder` document. What it has no answer to is *which* pack claims it, because
127
+ * that is not a property of the note — a folder materialises in every pack
128
+ * holding a document that references it, and its ancestors with it (#257). So
129
+ * it is exempt from the claim check for the opposite reason a homepage is:
130
+ * a homepage is in no pack, and a folder may be in several.
131
+ *
132
+ * @type {ReadonlySet<string>}
88
133
  */
89
- export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.mjs").DocumentSubtypeMap[];
134
+ export const DERIVED_PACKED_TYPES: ReadonlySet<string>;
135
+ export { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
90
136
  /**
91
137
  * What a claim question is asked against.
92
138
  *
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The document id a note compiles under: its pin, or its address.
3
+ *
4
+ * Returns `undefined` for a file with **no address** — no `type`, or no
5
+ * `shortcode`. Such a file is not an addressable note, so it has no document
6
+ * and inventing an id for one would file it under nothing. Every caller already
7
+ * had to handle an absent id (that is what the authored field's absence meant),
8
+ * so this reports the same thing rather than throwing where a walk used to
9
+ * skip; whether an id is *required* stays each pass's own decision
10
+ * (`BasePackCompiler.requiresId`).
11
+ *
12
+ * @param {object|null|undefined} fm - Parsed frontmatter.
13
+ * @param {object} [opts]
14
+ * @param {string} [opts.pkg] - The owning content package. Defaults to the
15
+ * configured one, which is the only package a tree's notes belong to (#56).
16
+ * @param {readonly object[]} [opts.maps] - The document-subtype maps, which
17
+ * decide the address's `<system>` segment.
18
+ * @returns {string|undefined} The document's `_id`, or `undefined` when the
19
+ * note has no address to derive one from.
20
+ */
21
+ export function noteDocId(fm: object | null | undefined, { pkg, maps }?: {
22
+ pkg?: string | undefined;
23
+ maps?: readonly object[] | undefined;
24
+ }): string | undefined;
25
+ /**
26
+ * Fill a note's `id` in place, so everything downstream reads one value.
27
+ *
28
+ * The corpus readers each hold their own parsed frontmatter and each ask for
29
+ * `fm.id` in several places; normalising the field once, where the note is
30
+ * read, is what makes "the id is derived" true for all of them rather than for
31
+ * whichever ones remembered to derive it. Idempotent, and a no-op for a note
32
+ * that authored an id or has no address.
33
+ *
34
+ * @param {object|null|undefined} fm - Parsed frontmatter, mutated in place.
35
+ * @param {object} [opts] - As {@link noteDocId}.
36
+ * @returns {object|null|undefined} `fm`, for chaining.
37
+ */
38
+ export function resolveNoteId(fm: object | null | undefined, opts?: object): object | null | undefined;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The shortcodes a note declares it used to be published under (#278).
3
+ *
4
+ * A package's `(type, shortcode)` addresses are a published interface, and
5
+ * `addresses diff` reports what a build stopped publishing — telling a
6
+ * **rename** from a **withdrawal** by matching document ids across two
7
+ * releases. #270 removed the property that rested on. An id is now derived from
8
+ * the canonical address, which carries the shortcode, so renaming a shortcode
9
+ * moves the id too: both sides of the join move together, the match finds
10
+ * nothing, and a rename is reported as a withdrawal with no successor named.
11
+ *
12
+ * **The remaining lever needed foresight, which is the wrong thing to ask for.**
13
+ * An authored `id:` still wins, and pinning one does keep a document's identity
14
+ * across a rename — but it has to be written *before* the rename, by an author
15
+ * who does not yet know they will make one. An author who has just renamed a
16
+ * shortcode knows exactly what the old one was, and that is the only moment
17
+ * anyone does.
18
+ *
19
+ * So a note states it:
20
+ *
21
+ * ```yaml
22
+ * type: weapongear
23
+ * shortcode: Taburi
24
+ * renamedFrom: Tabri
25
+ * ```
26
+ *
27
+ * **A declaration is an assertion, not a match, and the diagnostic says which
28
+ * it had.** That distinction is the whole ethos of `address-diff.mjs`: it
29
+ * refuses to guess a successor from a similar-looking string, because a wrong
30
+ * one sends the reader to the wrong fix. An author's declaration is neither a
31
+ * guess nor an identity match — it is testimony from the one party that knows —
32
+ * so it is used, attributed, and never silently blended with an id match.
33
+ *
34
+ * **It is a list, because renames chain.** The diff is release-to-release and a
35
+ * shortcode may be renamed more than once between two releases; a baseline
36
+ * several releases back published a name two renames ago. A single value would
37
+ * report that as a withdrawal again, which is the bug this closes.
38
+ *
39
+ * **It is transient.** Once every baseline a build is compared against post-dates
40
+ * the rename, the declaration has nothing left to say and may be deleted. That
41
+ * is the difference from an `id:` pin, which is permanent, opaque, and a second
42
+ * identity for a thing that already has one (#270).
43
+ *
44
+ * **It is one key per note, at the top level, however many systems the note
45
+ * compiles into.** A shortcode is the note's, not a system block's: a note
46
+ * carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
47
+ * shortcode, so a rename moves both and one declaration covers both.
48
+ *
49
+ * This module reads the key and says nothing about whether the values are
50
+ * *sound* — that a value is a well-formed shortcode, is not the note's own, and
51
+ * does not name an address some live note still publishes are cross-note
52
+ * questions, and `engine/content-lint.mjs` owns them. Composing a value into an
53
+ * address key is likewise not here: the diff defines that space and spells the
54
+ * key once, in `engine/address-diff.mjs`.
55
+ *
56
+ * @module
57
+ */
58
+ /**
59
+ * The `renamedFrom:` entries a note authors, exactly as authored.
60
+ *
61
+ * Scalar or list, because one predecessor is the overwhelmingly common case and
62
+ * requiring `- ` on it would be friction with nothing behind it; both normalize
63
+ * here so no reader has to ask which form it got.
64
+ *
65
+ * Entries are returned **unvalidated** — a number, a nested list, a blank
66
+ * string all come back as they were written. The lint needs to see them to
67
+ * report them, and a reader that quietly dropped them would report a correct
68
+ * tree clean while a typo silently did nothing.
69
+ *
70
+ * @param {object|null|undefined} fm - Parsed frontmatter.
71
+ * @returns {readonly unknown[]} The authored entries, in authored order; empty
72
+ * when the note declares none.
73
+ */
74
+ export function renamedFromEntries(fm: object | null | undefined): readonly unknown[];
75
+ /**
76
+ * The well-formed shortcodes among a note's `renamedFrom:` entries.
77
+ *
78
+ * Trimmed, de-duplicated, and in authored order. Anything that is not a
79
+ * non-blank string is skipped rather than coerced: it is reported by the lint,
80
+ * and a diff that guessed at what a number meant would claim a rename nobody
81
+ * declared.
82
+ *
83
+ * De-duplicating here rather than leaving it to callers is not tidiness — the
84
+ * predecessor index is a map, so a repeated entry would otherwise be indexed
85
+ * twice and the second write would look like a conflicting claim.
86
+ *
87
+ * @param {object|null|undefined} fm - Parsed frontmatter.
88
+ * @returns {string[]} The declared predecessor shortcodes.
89
+ */
90
+ export function renamedFrom(fm: object | null | undefined): string[];
91
+ /**
92
+ * Whether a note declares the key at all, however malformed its value.
93
+ *
94
+ * Separate from {@link renamedFrom} returning nothing, because the two mean
95
+ * opposite things to a lint: a note declaring no key is silent and correct,
96
+ * while one declaring `renamedFrom: []` — or a single blank string — has said
97
+ * something that does nothing, which is worth a word.
98
+ *
99
+ * @param {object|null|undefined} fm - Parsed frontmatter.
100
+ * @returns {boolean} `true` when the key is present and not null.
101
+ */
102
+ export function declaresRenamedFrom(fm: object | null | undefined): boolean;
@@ -199,7 +199,7 @@ export const NOTE_VOCABULARY: Readonly<{
199
199
  subTypes: readonly string[];
200
200
  data: readonly DataFieldSpec[];
201
201
  }>;
202
- armorgear: Readonly<{
202
+ armor: Readonly<{
203
203
  data: readonly DataFieldSpec[];
204
204
  }>;
205
205
  armorlocation: Readonly<{
@@ -209,7 +209,7 @@ export const NOTE_VOCABULARY: Readonly<{
209
209
  attribute: Readonly<{
210
210
  data: readonly DataFieldSpec[];
211
211
  }>;
212
- concoctiongear: Readonly<{
212
+ concoction: Readonly<{
213
213
  subTypes: readonly string[];
214
214
  data: readonly (DataFieldSpec | Readonly<{
215
215
  describe: "How many of the thing there are; one when unstated.";
@@ -237,7 +237,7 @@ export const NOTE_VOCABULARY: Readonly<{
237
237
  subTypes: readonly string[];
238
238
  data: readonly DataFieldSpec[];
239
239
  }>;
240
- projectilegear: Readonly<{
240
+ projectile: Readonly<{
241
241
  subTypes: readonly string[];
242
242
  data: readonly (DataFieldSpec | Readonly<{
243
243
  describe: "How many of the thing there are; one when unstated.";
@@ -264,6 +264,29 @@ export const NOTE_VOCABULARY: Readonly<{
264
264
  macro: Readonly<{
265
265
  data: readonly never[];
266
266
  }>;
267
+ folder: Readonly<{
268
+ data: readonly ({
269
+ describe: string;
270
+ shape: "a wikilink, or a map of wikilinks keyed by pack";
271
+ kind: "scalar-or-map";
272
+ entryShape: "a wikilink";
273
+ keys: "pack";
274
+ name: string;
275
+ } | {
276
+ describe: string;
277
+ shape: "string";
278
+ kind: "string";
279
+ name: string;
280
+ })[];
281
+ }>;
282
+ bundle: Readonly<{
283
+ data: readonly {
284
+ describe: string;
285
+ shape: "list of wikilinks";
286
+ kind: "list";
287
+ name: string;
288
+ }[];
289
+ }>;
267
290
  lore: Readonly<{
268
291
  subTypes: readonly string[];
269
292
  data: readonly never[];
@@ -349,17 +372,30 @@ export type DataFieldSpec = {
349
372
  */
350
373
  name: string;
351
374
  /**
352
- * - The value's
353
- * shape, for the lint. Absent means no claim is made about the value — which
354
- * is the honest answer wherever the specification's stated shape and the
355
- * shape notes are authored in today disagree.
375
+ * -
376
+ * The value's shape, for the lint. Absent means no claim is made about the
377
+ * value — which is the honest answer wherever the specification's stated
378
+ * shape and the shape notes are authored in today disagree.
356
379
  */
357
- kind?: "string" | "number" | "boolean" | "map" | "list" | undefined;
380
+ kind?: "string" | "number" | "boolean" | "map" | "list" | "scalar-or-map" | undefined;
358
381
  /**
359
382
  * - Human-readable shape, for a finding and for
360
383
  * documentation.
361
384
  */
362
385
  shape?: string | undefined;
386
+ /**
387
+ * - For a `scalar-or-map` field, what one
388
+ * entry of the map is. A finding names the entry at fault rather than
389
+ * quoting the whole map back, so the string an author has to correct is the
390
+ * one the message holds.
391
+ */
392
+ entryShape?: string | undefined;
393
+ /**
394
+ * - For a `scalar-or-map` field, what its keys name.
395
+ * `"pack"` means each is a pack this package declares, so a key naming none
396
+ * is a finding of its own: it addresses a hierarchy nothing will ever read.
397
+ */
398
+ keys?: "pack" | undefined;
363
399
  /**
364
400
  * - One line, for the author-facing reference.
365
401
  */
@@ -153,6 +153,43 @@ export function assertNoSectionField(fm: object | null | undefined, { file, absP
153
153
  file?: string | undefined;
154
154
  absPath?: string | undefined;
155
155
  }): void;
156
+ /**
157
+ * What a note writing a top-level `traits:` block is told.
158
+ *
159
+ * The message states the **mapping**, not just the destination, because three
160
+ * of the keys reshaped as well as moved: the block nested its measurements
161
+ * where the format flattens them. A bare "write `data:` instead" would send an
162
+ * author to author `data.height: {m: 1.78}`, which is a declared key holding an
163
+ * undeclared shape.
164
+ *
165
+ * @param {string} [file] - The note's path, named in the message. Omit it where
166
+ * the caller emits through a diagnostic, whose locator already starts the
167
+ * line — repeating it prints the path twice.
168
+ * @returns {string} The message, unpunctuated at the end as a finding is.
169
+ */
170
+ export function traitsRetiredMessage(file?: string): string;
171
+ /**
172
+ * Refuse a note that declares a top-level `traits:` block at all.
173
+ *
174
+ * Presence is the whole test, as it is for `draft:` and `aliases:`: an empty
175
+ * `traits:` is still a note claiming a block that no longer exists.
176
+ *
177
+ * @param {object|null|undefined} fm - Parsed frontmatter, or nothing when it
178
+ * could not be parsed.
179
+ * @param {object} [options] - Options.
180
+ * @param {string} [options.file] - The note's path, named in the message. Omit
181
+ * it where the caller emits through a diagnostic, which puts the locator at
182
+ * the start of the line already.
183
+ * @param {string} [options.absPath] - The note's file on disk, read only on the
184
+ * failing path to locate the offending line and column. The position rides on
185
+ * the thrown error as `position`, for a caller that emits a diagnostic.
186
+ * @returns {void}
187
+ * @throws {Error} When the note declares the block.
188
+ */
189
+ export function assertNoTraitsField(fm: object | null | undefined, { file, absPath }?: {
190
+ file?: string | undefined;
191
+ absPath?: string | undefined;
192
+ }): void;
156
193
  /**
157
194
  * A frontmatter key's position in a note's file, or nothing.
158
195
  *
@@ -196,6 +233,31 @@ export function locateFrontmatterKey(absPath: string | undefined, key: string, v
196
233
  * @returns {string} The message, unpunctuated at the end as a finding is.
197
234
  */
198
235
  export function retiredAliasMessage(retired: string, current: string, file?: string): string;
236
+ /**
237
+ * What a note writing a field at its **legacy in-block position** is told.
238
+ *
239
+ * The same three-step retirement `retiredAliasMessage` announces, applied to a
240
+ * *position* rather than a spelling (#305). A field whose shared source moved
241
+ * under `data:` keeps reading the key its block still carries, that read wins,
242
+ * and it is reported here so a sweep has something to count down — without it,
243
+ * moving a field into `data:` would be a flag day across every repository that
244
+ * authors it.
245
+ *
246
+ * It names the block, because the key is `hm3.species` and not `species`: an
247
+ * author told to move "`species`" has two regions to guess between.
248
+ *
249
+ * @param {string} block - The system block the key was written in.
250
+ * @param {{name?: string, legacyKey?: string}} field - The declaration, which
251
+ * names both positions.
252
+ * @param {string} [file] - The note's path, named in the message. Omit it where
253
+ * the caller emits through a diagnostic, whose locator already starts the
254
+ * line — repeating it prints the path twice.
255
+ * @returns {string} The message, unpunctuated at the end as a finding is.
256
+ */
257
+ export function legacyKeyMessage(block: string, field: {
258
+ name?: string;
259
+ legacyKey?: string;
260
+ }, file?: string): string;
199
261
  /**
200
262
  * Whether a note writes the retired spelling of a field, wherever it put it.
201
263
  *
@@ -236,6 +298,19 @@ export function readAliasedField(fm: object | null | undefined, current: string)
236
298
  * `image` is retired on a map — which declares `img` — and remains an unknown
237
299
  * key anywhere else.
238
300
  *
301
+ * **`templatePriority` (#266).** The number that decides which of several
302
+ * competing templates the Create dialog offers was called `archetype`, and
303
+ * `archetypes` — one letter apart — is a list of what *sort* a character is.
304
+ * A priority and a taxonomy cannot be told apart by a plural `s`, so the
305
+ * priority takes the name that says what it is. The specification already
306
+ * called it `templatePriority` on two of its three sides.
307
+ *
308
+ * **`relations` (SoHL#1781).** The field holds a *map* of standings, one per
309
+ * affiliation — its own description says so, and `resolveRelation` has always
310
+ * read it that way. `relation` named the many as one, which every author had to
311
+ * read past. Only `affiliation` declares the field, so the alias is reported
312
+ * there and the old spelling stays an ordinary unknown key everywhere else.
313
+ *
239
314
  * **`img` (#142).** Every note type names its artwork `img`, at the note's top
240
315
  * level, and resolves it the same way. A map alone named its background art
241
316
  * `image` and read it out of the `sohl:` block — two spellings for one idea,
@@ -14,9 +14,10 @@
14
14
  */
15
15
  export function collectKnownActionNames(repoRoot: string): Set<string>;
16
16
  export class Scenes extends BasePackCompiler {
17
- constructor({ contentBase, dest, companionDests, folderResolver, repoRoot, }: {
17
+ constructor({ contentBase, dest, skipDirectories, companionDests, folderResolver, repoRoot, }: {
18
18
  contentBase: any;
19
19
  dest: any;
20
+ skipDirectories: any;
20
21
  companionDests?: {} | undefined;
21
22
  folderResolver?: (() => null) | undefined;
22
23
  repoRoot?: string | undefined;
@@ -30,7 +31,7 @@ export class Scenes extends BasePackCompiler {
30
31
  */
31
32
  adventureCount: number;
32
33
  index: Map<string, object> | undefined;
33
- effectsByAddress: Map<string, object> | undefined;
34
+ effectsByAddress: Map<any, any> | undefined;
34
35
  knownActions: Set<string> | undefined;
35
36
  /** place key → `{name, img, scenes: [], journal: []}` */
36
37
  places: Map<any, any> | undefined;
@@ -93,7 +93,7 @@ export function compareFields({ builders, artifact, documentType, subtypeOf, }:
93
93
  * *observed*. {@link compareFields} reads the `itemBuilders` declarations and
94
94
  * {@link checkAuthoredSystemData} reads a note's `<system>.system`; between them
95
95
  * they miss every key a compiler writes on its own initiative, which is not a
96
- * residue — it is `shortcode`, `actionDefs`, `notes`, `docHtml` and `archetype`.
96
+ * residue — it is `shortcode`, `actionDefs`, `notes`, `docHtml` and `templatePriority`.
97
97
  *
98
98
  * **The keys come from the object the compiler built, after a JSON round trip.**
99
99
  * That is exactly what the pack file receives, so a key whose value is
@@ -159,11 +159,24 @@ export function compareEmittedSystem({ system, artifact, documentType, subtype,
159
159
  * stamps no system at all, and a system that has not adopted the artifact yet
160
160
  * is simply unchecked. Neither is an error, and the caller says which it was.
161
161
  *
162
+ * **A build may have more than one system, and then the caller names it (#139).**
163
+ * `stats.systemId` is the package-wide answer, and a repository shipping content
164
+ * for two systems has no package-wide answer — it is deliberately `null` there,
165
+ * because a module feeding both `sohl` and `hm3` targets neither. Left at that,
166
+ * every schema check in such a build would be skipped in silence, which is the
167
+ * state #60 exists to remove: the five type names the two systems *share* are
168
+ * exactly the ones a wrong-system emission hides in. So a pass supplies the
169
+ * system its pack declares, and the version comes from that system's own
170
+ * `systems:` entry rather than from a package-wide stamp.
171
+ *
162
172
  * @param {object} config - The resolved build configuration.
173
+ * @param {string|null} [system] - The system whose schema is wanted. Defaults
174
+ * to the package-wide `stats.systemId`, which is every single-system build
175
+ * and the behaviour this always had.
163
176
  * @returns {{artifact: SchemaArtifact, source: string}|null} The schema and
164
177
  * where it was read from.
165
178
  */
166
- export function resolveSchemaArtifact(config: object): {
179
+ export function resolveSchemaArtifact(config: object, system?: string | null): {
167
180
  artifact: SchemaArtifact;
168
181
  source: string;
169
182
  } | null;
@@ -238,14 +251,18 @@ export function emittedUndeclaredMessage(finding: EmissionFinding): string;
238
251
  * @param {string} opts.block - The system block to read, e.g. `"sohl"`.
239
252
  * @param {string} opts.documentType - `Item`, `Actor`, …
240
253
  * @param {string} opts.subType - The document subtype the note compiles into.
254
+ * @param {string} [opts.system] - The system whose published schema to read,
255
+ * where a build has more than one (#139). Defaults to the package-wide
256
+ * `stats.systemId`.
241
257
  * @param {object} [opts.config] - The resolved build configuration.
242
258
  * @returns {{path: string, message: string}[]} One finding per undeclared path,
243
259
  * shallowest-first.
244
260
  */
245
- export function checkAuthoredSystemData(fm: object, { block, documentType, subType, config }: {
261
+ export function checkAuthoredSystemData(fm: object, { block, documentType, subType, system, config }: {
246
262
  block: string;
247
263
  documentType: string;
248
264
  subType: string;
265
+ system?: string | undefined;
249
266
  config?: object | undefined;
250
267
  }): {
251
268
  path: string;
@@ -275,10 +292,13 @@ export function checkAuthoredSystemData(fm: object, { block, documentType, subTy
275
292
  * @param {string} opts.type - The note's content type, for the message.
276
293
  * @param {readonly {to?: string}[]} [opts.fields] - The type's field
277
294
  * declaration, which decides each finding's origin.
295
+ * @param {string} [opts.system] - The system whose published schema to read,
296
+ * where a build has more than one (#139). Defaults to the package-wide
297
+ * `stats.systemId`.
278
298
  * @param {object} [opts.config] - The resolved build configuration.
279
299
  * @returns {(EmissionFinding & {message: string})[]} One per undeclared path.
280
300
  */
281
- export function checkEmittedSystemData(system: object, { fm, block, documentType, subType, type, fields, config }: {
301
+ export function checkEmittedSystemData(emitted: any, { fm, block, documentType, subType, type, fields, system, config, }: {
282
302
  fm: object;
283
303
  block: string;
284
304
  documentType: string;
@@ -287,6 +307,7 @@ export function checkEmittedSystemData(system: object, { fm, block, documentType
287
307
  fields?: readonly {
288
308
  to?: string;
289
309
  }[] | undefined;
310
+ system?: string | undefined;
290
311
  config?: object | undefined;
291
312
  }): (EmissionFinding & {
292
313
  message: string;
@@ -127,10 +127,10 @@ export function writeHomepages(outRoot: string, pages: readonly object[], config
127
127
  * @param {object[]} pages - Every page, from both walks.
128
128
  * @param {object} findings - `{ addressFindings, fmLinkFindings }` from
129
129
  * collection.
130
- * @param {object} options - `{ manifestDir }`.
130
+ * @param {object} options - `{ config }`.
131
131
  * @returns {object} The gate results and, when they pass, the built index.
132
132
  */
133
- export function siteGates(pages: object[], findings: object, { manifestDir }: object): object;
133
+ export function siteGates(pages: object[], findings: object, { config }: object): object;
134
134
  /**
135
135
  * The gate result of a build that ran none of them.
136
136
  *
@@ -374,7 +374,7 @@ export function resolveOutputRoot(rootDir: string, out: string): string;
374
374
  * @returns {{gates: object, stats: object|null, tableErrors: object[],
375
375
  * wikiErrors: object[], manifests: object|null}}
376
376
  */
377
- export function buildSite({ config, outRoot }?: {
377
+ export function buildSite({ config, outRoot, sqlTables }?: {
378
378
  config?: object | undefined;
379
379
  outRoot?: string | undefined;
380
380
  }): {
@@ -385,4 +385,4 @@ export function buildSite({ config, outRoot }?: {
385
385
  manifests: object | null;
386
386
  };
387
387
  export { formatUnaddressableFinding };
388
- import { formatUnaddressableFinding } from "./foreign-manifests.mjs";
388
+ import { formatUnaddressableFinding } from "./metadata-index.mjs";
@@ -5,7 +5,7 @@
5
5
  * each already knowing its own `url`.
6
6
  * @param {object} [options] - Cross-package inputs.
7
7
  * @param {Map<string, {package: string, type?: string}>} [options.foreignIndex]
8
- * The merged index from `loadForeignManifests`. Omit when the build publishes
8
+ * The merged index from `loadForeignIndexes`. Omit when the build publishes
9
9
  * no cross-package links.
10
10
  * @returns {SiteIndex} The index, and what could not be addressed unambiguously.
11
11
  */