@heroiclands/package-build 20.3.1 → 20.5.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 (195) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/CONTENT.md +322 -110
  3. package/README.md +21 -10
  4. package/bin/content-build.mjs +204 -92
  5. package/bin/package-build.mjs +48 -15
  6. package/config.mjs +6 -8
  7. package/content-config.mjs +518 -151
  8. package/docs/api.md +1352 -0
  9. package/docs/commands.md +1609 -0
  10. package/docs/configuration.md +1432 -0
  11. package/docs/content-format.md +73 -74
  12. package/docs/diagnostics.md +356 -0
  13. package/docs/getting-started.md +813 -0
  14. package/docs/project-setup.md +469 -0
  15. package/e2e.mjs +4 -4
  16. package/engine/actor-compiler.mjs +64 -61
  17. package/engine/address-charset.mjs +6 -6
  18. package/engine/address-diff.mjs +57 -52
  19. package/engine/anchored-sections.mjs +1 -1
  20. package/engine/anchors.mjs +1 -1
  21. package/engine/base-compiler.mjs +42 -38
  22. package/engine/bundle-notes.mjs +4 -4
  23. package/engine/bundles.mjs +17 -8
  24. package/engine/code-fences.mjs +3 -3
  25. package/engine/compendiums.mjs +8 -8
  26. package/engine/compile-corpus.mjs +4 -5
  27. package/engine/content-address.mjs +24 -24
  28. package/engine/content-charset.mjs +1 -1
  29. package/engine/content-format-check.mjs +24 -25
  30. package/engine/content-format.mjs +8 -8
  31. package/engine/content-html.mjs +202 -0
  32. package/engine/content-icons.mjs +196 -165
  33. package/engine/content-index.mjs +61 -40
  34. package/engine/content-links.mjs +74 -41
  35. package/engine/content-lint.mjs +64 -34
  36. package/engine/content-package.mjs +7 -9
  37. package/engine/content-slug.mjs +2 -2
  38. package/engine/content-tables.mjs +38 -34
  39. package/engine/content-tree.mjs +1 -1
  40. package/engine/derived-fields.mjs +174 -0
  41. package/engine/diagnostics.mjs +3 -4
  42. package/engine/document-subtypes.mjs +6 -7
  43. package/engine/field-reference.mjs +8 -8
  44. package/engine/field-spec.mjs +29 -30
  45. package/engine/folder-notes.mjs +18 -16
  46. package/engine/foreign-catalog.mjs +8 -9
  47. package/engine/foundry-entries.mjs +19 -20
  48. package/engine/frontmatter-lint.mjs +95 -103
  49. package/engine/frontmatter.mjs +11 -11
  50. package/engine/generate.mjs +44 -39
  51. package/engine/helpers.mjs +147 -84
  52. package/engine/homepage.mjs +20 -22
  53. package/engine/ids.mjs +15 -15
  54. package/engine/index-records.mjs +4 -4
  55. package/engine/index.mjs +26 -10
  56. package/engine/item-compiler.mjs +54 -29
  57. package/engine/item-docs.mjs +11 -13
  58. package/engine/item-registry.mjs +13 -13
  59. package/engine/journals.mjs +24 -15
  60. package/engine/macros.mjs +3 -3
  61. package/engine/map-notes.mjs +6 -6
  62. package/engine/metadata-index.mjs +5 -5
  63. package/engine/note-claims.mjs +51 -41
  64. package/engine/note-ids.mjs +5 -5
  65. package/engine/note-package.mjs +4 -4
  66. package/engine/note-renames.mjs +3 -3
  67. package/engine/note-schemas.mjs +3 -8
  68. package/engine/note-vocabulary.mjs +56 -54
  69. package/engine/pack-config.mjs +39 -25
  70. package/engine/pack-router.mjs +0 -0
  71. package/engine/pdf-build.mjs +464 -0
  72. package/engine/pdf-fonts.mjs +420 -0
  73. package/engine/pdf-render.mjs +798 -0
  74. package/engine/pdf-toc.mjs +525 -0
  75. package/engine/prose-config.mjs +9 -9
  76. package/engine/prose-lint.mjs +12 -13
  77. package/engine/region-events.mjs +2 -2
  78. package/engine/retired-fields.mjs +24 -22
  79. package/engine/runtime-only-fields.mjs +1 -1
  80. package/engine/scene-levels.mjs +1 -1
  81. package/engine/scenes.mjs +33 -25
  82. package/engine/schema-check.mjs +21 -21
  83. package/engine/schema-extract.mjs +1 -1
  84. package/engine/site-build.mjs +66 -50
  85. package/engine/site-index.mjs +17 -18
  86. package/engine/sql-tables.mjs +21 -13
  87. package/engine/subtype-registry.mjs +5 -4
  88. package/engine/system-block.mjs +18 -19
  89. package/engine/systems.mjs +2 -2
  90. package/engine/web-wikilinks.mjs +41 -38
  91. package/engine/wikilink-syntax.mjs +16 -16
  92. package/engine/wikilinks.mjs +40 -42
  93. package/hm3/actors.mjs +30 -22
  94. package/hm3/document-subtypes.mjs +5 -5
  95. package/hm3/index.mjs +2 -2
  96. package/hm3/item-builders.mjs +1 -1
  97. package/hm3/item-fields.mjs +3 -3
  98. package/hm3/items.mjs +28 -8
  99. package/hm3/template-priority.mjs +2 -2
  100. package/lang.mjs +3 -3
  101. package/manifest.mjs +17 -20
  102. package/package.json +1 -2
  103. package/release.mjs +66 -6
  104. package/sohl/actors.mjs +36 -28
  105. package/sohl/affiliation-standings.mjs +2 -2
  106. package/sohl/being-info.mjs +5 -5
  107. package/sohl/default-item-art.mjs +5 -5
  108. package/sohl/document-subtypes.mjs +5 -5
  109. package/sohl/index.mjs +3 -3
  110. package/sohl/item-builders.mjs +7 -7
  111. package/sohl/item-fields.mjs +8 -9
  112. package/sohl/items.mjs +28 -6
  113. package/sohl/kb-passes.mjs +5 -5
  114. package/sohl/note-schemas.mjs +12 -12
  115. package/sohl/skill-base.mjs +1 -1
  116. package/types/content-config.d.mts +91 -39
  117. package/types/e2e.d.mts +3 -3
  118. package/types/engine/actor-compiler.d.mts +45 -40
  119. package/types/engine/address-charset.d.mts +6 -6
  120. package/types/engine/address-diff.d.mts +60 -6
  121. package/types/engine/base-compiler.d.mts +27 -21
  122. package/types/engine/bundle-notes.d.mts +3 -3
  123. package/types/engine/bundles.d.mts +10 -1
  124. package/types/engine/code-fences.d.mts +3 -3
  125. package/types/engine/compendiums.d.mts +3 -3
  126. package/types/engine/compile-corpus.d.mts +1 -1
  127. package/types/engine/content-address.d.mts +20 -20
  128. package/types/engine/content-format-check.d.mts +6 -6
  129. package/types/engine/content-format.d.mts +2 -2
  130. package/types/engine/content-html.d.mts +78 -0
  131. package/types/engine/content-icons.d.mts +139 -92
  132. package/types/engine/content-index.d.mts +59 -15
  133. package/types/engine/content-links.d.mts +8 -8
  134. package/types/engine/content-lint.d.mts +8 -6
  135. package/types/engine/content-package.d.mts +6 -8
  136. package/types/engine/content-tables.d.mts +49 -18
  137. package/types/engine/derived-fields.d.mts +101 -0
  138. package/types/engine/diagnostics.d.mts +2 -2
  139. package/types/engine/document-subtypes.d.mts +3 -3
  140. package/types/engine/field-spec.d.mts +37 -39
  141. package/types/engine/folder-notes.d.mts +4 -5
  142. package/types/engine/foreign-catalog.d.mts +3 -3
  143. package/types/engine/foundry-entries.d.mts +6 -7
  144. package/types/engine/frontmatter-lint.d.mts +10 -9
  145. package/types/engine/frontmatter.d.mts +7 -7
  146. package/types/engine/generate.d.mts +7 -7
  147. package/types/engine/helpers.d.mts +128 -56
  148. package/types/engine/homepage.d.mts +16 -18
  149. package/types/engine/ids.d.mts +13 -13
  150. package/types/engine/index-records.d.mts +3 -3
  151. package/types/engine/index.d.mts +6 -0
  152. package/types/engine/item-compiler.d.mts +21 -5
  153. package/types/engine/item-docs.d.mts +2 -2
  154. package/types/engine/item-registry.d.mts +6 -6
  155. package/types/engine/journals.d.mts +12 -3
  156. package/types/engine/map-notes.d.mts +2 -2
  157. package/types/engine/metadata-index.d.mts +4 -4
  158. package/types/engine/note-claims.d.mts +30 -22
  159. package/types/engine/note-ids.d.mts +4 -4
  160. package/types/engine/note-package.d.mts +1 -1
  161. package/types/engine/note-renames.d.mts +3 -3
  162. package/types/engine/note-vocabulary.d.mts +31 -203
  163. package/types/engine/pack-config.d.mts +7 -7
  164. package/types/engine/pack-router.d.mts +1 -1
  165. package/types/engine/pdf-build.d.mts +42 -0
  166. package/types/engine/pdf-fonts.d.mts +30 -0
  167. package/types/engine/pdf-render.d.mts +144 -0
  168. package/types/engine/pdf-toc.d.mts +114 -0
  169. package/types/engine/prose-config.d.mts +9 -9
  170. package/types/engine/prose-lint.d.mts +3 -4
  171. package/types/engine/region-events.d.mts +2 -2
  172. package/types/engine/retired-fields.d.mts +10 -9
  173. package/types/engine/scenes.d.mts +10 -1
  174. package/types/engine/schema-check.d.mts +13 -13
  175. package/types/engine/site-build.d.mts +52 -25
  176. package/types/engine/site-index.d.mts +3 -4
  177. package/types/engine/sql-tables.d.mts +11 -5
  178. package/types/engine/subtype-registry.d.mts +3 -3
  179. package/types/engine/system-block.d.mts +3 -3
  180. package/types/engine/web-wikilinks.d.mts +7 -7
  181. package/types/engine/wikilink-syntax.d.mts +17 -17
  182. package/types/engine/wikilinks.d.mts +14 -14
  183. package/types/hm3/actors.d.mts +9 -1
  184. package/types/hm3/document-subtypes.d.mts +1 -1
  185. package/types/hm3/items.d.mts +9 -2
  186. package/types/hm3/template-priority.d.mts +1 -1
  187. package/types/manifest.d.mts +8 -8
  188. package/types/release.d.mts +15 -4
  189. package/types/sohl/actors.d.mts +12 -4
  190. package/types/sohl/affiliation-standings.d.mts +2 -2
  191. package/types/sohl/being-info.d.mts +2 -2
  192. package/types/sohl/document-subtypes.d.mts +1 -1
  193. package/types/sohl/items.d.mts +9 -2
  194. package/types/sohl/note-schemas.d.mts +1 -1
  195. package/MIGRATING.md +0 -608
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Validate the raw tree and flatten it into nodes.
3
+ *
4
+ * The format has no chapter/section distinction: every node is a section, and
5
+ * the leaves of the *document* are the notes and prose its contents resolve to.
6
+ * Depth is whatever the tree says, so nothing here caps or normalises it.
7
+ *
8
+ * `contents` is an **ordered, heterogeneous** list — prose, filters and child
9
+ * sections interleave in the sequence written, and that sequence is preserved
10
+ * exactly. A section that opens with a `file:` and then lists its entries is
11
+ * saying something different from one that does the reverse.
12
+ *
13
+ * @param {unknown} raw - The parsed document, as YAML returns it.
14
+ * @param {object} [opts] - Options.
15
+ * @param {string} [opts.text] - The file's source, for finding positions. Every
16
+ * finding without one is still reported, just without a locator.
17
+ * @returns {{nodes: object[], findings: object[]}} The flattened tree and what
18
+ * was wrong with it.
19
+ */
20
+ export function parseDocumentTree(raw: unknown, { text }?: {
21
+ text?: string | undefined;
22
+ }): {
23
+ nodes: object[];
24
+ findings: object[];
25
+ };
26
+ /**
27
+ * Run every filter, and report the ones that would not run.
28
+ *
29
+ * The I/O half, kept apart from the planner for the reason the whole engine
30
+ * keeps them apart: the plan is then assertable without a database. The build
31
+ * owns the statement — `SELECT * FROM notes WHERE <clause>` — which is what
32
+ * makes a filter unable to reach another package's schema, unable to project
33
+ * something that is not a note, and unable to pick up the `doc<type>`
34
+ * documentation rows that ride the same index as the notes they document.
35
+ *
36
+ * **A filter that selects nothing is an error**, and the distinction that makes
37
+ * that consistent is worth stating. A *note* no clause selects is expected: the
38
+ * book is a selection and a project decides what its own volume carries. A
39
+ * *clause* that selects no note is not the same thing — a filter is a deliberate
40
+ * act, so one matching nothing is either wrong or left over from a structure
41
+ * that has moved on, and in both cases the tree should not carry it. Reported
42
+ * with the section's name and the filter's position, so the choice between
43
+ * fixing it and deleting it is the author's.
44
+ *
45
+ * @param {object[]} nodes - From {@link parseDocumentTree}.
46
+ * @param {{query: (sql: string) => Promise<{rows: object[]}>}} db - An open
47
+ * database, from {@link module:engine/sql-tables.openNotesDatabase}.
48
+ * @param {object} [opts] - Options.
49
+ * @param {(record: object) => boolean} [opts.keep] - Which rows are notes.
50
+ * @param {string} [opts.text] - The document's source, for finding positions.
51
+ * @returns {Promise<{selections: Map<string, object[]>, findings: object[]}>}
52
+ */
53
+ export function runTreeFilters(nodes: object[], db: {
54
+ query: (sql: string) => Promise<{
55
+ rows: object[];
56
+ }>;
57
+ }, { keep, text }?: {
58
+ keep?: ((record: object) => boolean) | undefined;
59
+ text?: string | undefined;
60
+ }): Promise<{
61
+ selections: Map<string, object[]>;
62
+ findings: object[];
63
+ }>;
64
+ /**
65
+ * Resolve the flattened tree into the document plan.
66
+ *
67
+ * The plan is an ordered list of entries — `section`, `prose`, `note` — and it
68
+ * is the artifact worth having. Order, depth, the outline, the table of
69
+ * contents, anchor uniqueness and every link destination are all readable from
70
+ * it, so nearly the whole of what #316 asks for can be asserted here, on data,
71
+ * without a renderer or a PDF. Only how the result *looks* needs eyes.
72
+ *
73
+ * **Notes are sorted, prose is not.** A section's entries come out in
74
+ * `nameAscii` order — the ASCII fold `buildIndexRecord` already derives, so a
75
+ * circumflex sorts with its letter instead of after `Z` as a raw codepoint
76
+ * comparison would put it. Prose keeps the position it was written in, because
77
+ * its place in the sequence is the author's statement.
78
+ *
79
+ * **A section with nothing in it does not print**, and emptiness is judged after
80
+ * its descendants are: a section holding only sections that all resolved to
81
+ * nothing is itself empty. In a correct tree this never fires — a filter that
82
+ * selects nothing is reported by {@link runTreeFilters} as the error it is — so
83
+ * this is the graceful half of that failure rather than a feature: a build whose
84
+ * filters are broken still produces a readable document to look at while they
85
+ * are fixed. A section holding only prose is not empty; it is a preface.
86
+ *
87
+ * @param {object[]} nodes - From {@link parseDocumentTree}.
88
+ * @param {object} [opts] - Options.
89
+ * @param {Map<string, object[]>} [opts.selections] - Records each filter
90
+ * selected, keyed by item id. Run by the caller: DuckDB is async and this is
91
+ * not.
92
+ * @returns {{entries: object[], links: Map<string, string>, stats: object}}
93
+ * The plan, the address→anchor map inbound wikilinks resolve through, and
94
+ * what the selection came to.
95
+ */
96
+ export function planDocument(nodes: object[], { selections }?: {
97
+ selections?: Map<string, object[]> | undefined;
98
+ }): {
99
+ entries: object[];
100
+ links: Map<string, string>;
101
+ stats: object;
102
+ };
103
+ /**
104
+ * Presentation a node may declare, and that its descendants inherit.
105
+ *
106
+ * Reserved now although the first release renders none of them, because the
107
+ * shape of the file is the thing consumers commit to: a book that has to be
108
+ * restructured to gain a running head has the wrong format, not the wrong
109
+ * renderer. Inheritance is what makes them worth declaring at all — `Gear` says
110
+ * once which infobox its entries use, and nine sections beneath it agree.
111
+ *
112
+ * @type {readonly string[]}
113
+ */
114
+ export const PRESENTATION_KEYS: readonly string[];
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @param {string} file - Path of the file about to be formatted.
8
8
  * @returns {object} Options to hand Prettier directly. Never carries
9
- * `overrides`: passing that inline is what silently did nothing (#76).
9
+ * `overrides`: passing that inline is what silently did nothing.
10
10
  */
11
11
  export function sharedPrettierOptionsFor(file: string): object;
12
12
  /**
@@ -15,11 +15,11 @@ export function sharedPrettierOptionsFor(file: string): object;
15
15
  * The runner resolves each file's options as *either* the consumer's own config
16
16
  * or {@link sharedPrettierOptionsFor}, never a merge. That is what bare Prettier
17
17
  * does and it is the contract — but it means the conventions this package exists
18
- * to publish hold by convention alone, and they lapse in two opposite directions
19
- * (#133). A consumer that declares any config of its own gets whatever that
18
+ * to publish hold by convention alone, and they lapse in two opposite directions.
19
+ * A consumer that declares any config of its own gets whatever that
20
20
  * config says: spread {@link PRETTIER_BASE} without the markdown override and
21
- * every note reindents at 4, the reindentation the override was added to prevent
22
- * (#76); write a partial `.prettierrc` such as `{"tabWidth": 2}` and
21
+ * every note reindents at 4, the reindentation the override was added to prevent;
22
+ * write a partial `.prettierrc` such as `{"tabWidth": 2}` and
23
23
  * `printWidth`, `trailingComma`, `experimentalTernaries` and the rest fall back
24
24
  * to Prettier's own defaults. A consumer that declares *nothing* formats one way
25
25
  * under this command and another under a bare `npx prettier`.
@@ -53,14 +53,14 @@ export function sharedPrettierDivergence(resolved: object | null | undefined, fi
53
53
  /**
54
54
  * The prose conventions every content repository writes to — one Prettier
55
55
  * configuration and one markdownlint rule set, declared here so a note
56
- * formatted in one repository is formatted the same way in the next (#69).
56
+ * formatted in one repository is formatted the same way in the next.
57
57
  *
58
- * These used to exist in exactly one consumer. The SoHL repository carried
58
+ * These belong in no single consumer. The SoHL repository carried
59
59
  * both; `sohl-thalorna` had Prettier but never ran it from `lint`; and
60
60
  * `sohl-kethira-basic` had neither, so the package least likely to have been
61
61
  * proofread was checked for addresses and nothing else. A rule set copied into
62
62
  * three repositories is one rule with three implementations, which is the drift
63
- * #20 exists to remove — so it is declared once, here, and every consumer
63
+ * this exists to remove — so it is declared once, here, and every consumer
64
64
  * invokes it.
65
65
  *
66
66
  * **Neither of these is an override.** A consumer that declares its own
@@ -95,7 +95,7 @@ export const PRETTIER_BASE: Readonly<object>;
95
95
  * **Declared apart from the `overrides` block, not derived from it.** Prettier
96
96
  * applies `overrides` only while resolving a config *file*; options handed to
97
97
  * it directly keep the global values, so a consumer with no config of its own
98
- * silently got markdown at 4 (#76). The runner needs the adjustment as data it
98
+ * silently got markdown at 4. The runner needs the adjustment as data it
99
99
  * can apply itself, and {@link PRETTIER_CONFIG} composes the same values into
100
100
  * the shape a config file wants — one source, two presentations.
101
101
  *
@@ -14,8 +14,7 @@
14
14
  * @param {boolean} [opts.write=false] - Rewrite unformatted files in place
15
15
  * rather than reporting them. Each file is formatted to a fixpoint (up to
16
16
  * {@link MAX_FORMAT_PASSES} passes), so a written tree is one a second run
17
- * leaves alone; a file that will not converge is reported and left unchanged
18
- * (#125).
17
+ * leaves alone; a file that will not converge is reported and left unchanged.
19
18
  * @param {object} [opts.prettier] - The Prettier module, for tests.
20
19
  * @returns {Promise<{findings: Array<{file: string, severity: string,
21
20
  * message: string}>, checked: number, written: string[]}>} The findings, how
@@ -37,7 +36,7 @@ export function checkFormatting(root: string, opts?: {
37
36
  }>;
38
37
  /**
39
38
  * Report where a repository's own Prettier configuration parts from the shared
40
- * one — or that it has none at all (#133).
39
+ * one — or that it has none at all.
41
40
  *
42
41
  * **Warnings, every one of them.** A consumer's config wins by design and this
43
42
  * does not change that; it only refuses to let the divergence be silent, which
@@ -58,7 +57,7 @@ export function checkFormatting(root: string, opts?: {
58
57
  * @returns {Promise<{findings: Array<{file?: string, severity: string,
59
58
  * message: string}>, configFile: string|null}>} The findings and the config
60
59
  * file they are about, which is `null` when the repository declares none. A
61
- * finding about a missing file carries no `file`: #17's rule is to drop a
60
+ * finding about a missing file carries no `file`: the rule is to drop a
62
61
  * field rather than invent one.
63
62
  */
64
63
  export function checkPrettierConventions(root: string, opts?: {
@@ -9,10 +9,10 @@
9
9
  * runtime bridge from drifting apart.
10
10
  *
11
11
  * It sits in this package rather than in the system's `src/` tree because the
12
- * map-note compiler that reads it is installed as a dependency (#1501), and a
12
+ * map-note compiler that reads it is installed as a dependency, and a
13
13
  * relative path out of the package would resolve to garbage from
14
14
  * `node_modules`. The runtime reaches it back through the package's
15
- * `./engine/region-events` entry point (#1510). It is engine-side, not
15
+ * `./engine/region-events` entry point. It is engine-side, not
16
16
  * SoHL-side, because any content module that authors a scene region — an
17
17
  * adventure module included — needs this vocabulary.
18
18
  *
@@ -43,11 +43,11 @@ export function assertNoDraftField(fm: object | null | undefined, { file, absPat
43
43
  * It says what the field fed and what to write instead, rather than which value
44
44
  * to correct: no value makes declaring it right.
45
45
  *
46
- * **What it did (#180).** It was the authored half of the alias index — the
46
+ * **What it did.** It was the authored half of the alias index — the
47
47
  * namespace a bare `[[Alias]]` was looked up in. Across the three content trees
48
48
  * not one bare link resolved through it, while the collision rule that kept it
49
49
  * unambiguous folded in every note's `name.full` and so dictated what a note
50
- * could be named (#179). The form is retired, so the list has no reader.
50
+ * could be named. The form is retired, so the list has no reader.
51
51
  *
52
52
  * **`name.aliases` is a different field and is not retired.** It fed the same
53
53
  * index, but unlike the top-level list it is being kept — reserved, unread,
@@ -114,10 +114,10 @@ export function declaresRetiredAliasesField(fm: object | null | undefined): bool
114
114
  * It names what lands a section now rather than a value to correct: no value
115
115
  * makes declaring the field right.
116
116
  *
117
- * **What it did (#202).** It named the section a `collection` note headed,
117
+ * **What it did.** It named the section a `collection` note headed,
118
118
  * under the `collection` landing rule — the only reader it ever had, in the
119
119
  * second branch of `landingOf` (`engine/content-address.mjs`). That rule went
120
- * first, and the whole mechanism went with it (#204): a section is a Hugo
120
+ * first, and the whole mechanism went with it: a section is a Hugo
121
121
  * directory the note format does not carry, so no note lands one and a page
122
122
  * that introduces a type is an ordinary note addressed `doc-<type>`. Nothing
123
123
  * else read the field, and no schema or vocabulary declared it, so left in
@@ -237,7 +237,7 @@ export function retiredAliasMessage(retired: string, current: string, file?: str
237
237
  * What a note writing a field at its **legacy in-block position** is told.
238
238
  *
239
239
  * The same three-step retirement `retiredAliasMessage` announces, applied to a
240
- * *position* rather than a spelling (#305). A field whose shared source moved
240
+ * *position* rather than a spelling. A field whose shared source moved
241
241
  * under `data:` keeps reading the key its block still carries, that read wins,
242
242
  * and it is reported here so a sweep has something to count down — without it,
243
243
  * moving a field into `data:` would be a flag day across every repository that
@@ -260,9 +260,10 @@ export function legacyKeyMessage(block: string, field: {
260
260
  }, file?: string): string;
261
261
  /**
262
262
  * What a note writing a field at the **top-level key `data:` gathered it off**
263
- * is told (#332).
263
+ * is told.
264
264
  *
265
- * {@link legacyKeyMessage}'s counterpart for the other retiring position. #128
265
+ * {@link legacyKeyMessage}'s counterpart for the other retiring position. The
266
+ * sweep
266
267
  * did not invent the facts `data:` holds — it collected them out of the note's
267
268
  * open top level — so `portrait:` beside `img:` is the *pre-`data:`* spelling
268
269
  * of `data.portrait`, and both are read for the same reason both in-block
@@ -322,14 +323,14 @@ export function readAliasedField(fm: object | null | undefined, current: string)
322
323
  * `relation` is retired on an affiliation — which declares `relations` — and
323
324
  * remains an unknown key anywhere else.
324
325
  *
325
- * **`templatePriority` (#266).** The number that decides which of several
326
+ * **`templatePriority`.** The number that decides which of several
326
327
  * competing templates the Create dialog offers was called `archetype`, and
327
328
  * `archetypes` — one letter apart — is a list of what *sort* a character is.
328
329
  * A priority and a taxonomy cannot be told apart by a plural `s`, so the
329
330
  * priority takes the name that says what it is. The specification already
330
331
  * called it `templatePriority` on two of its three sides.
331
332
  *
332
- * **`relations` (SoHL#1781).** The field holds a *map* of standings, one per
333
+ * **`relations`.** The field holds a *map* of standings, one per
333
334
  * affiliation — its own description says so, and `resolveRelation` has always
334
335
  * read it that way. `relation` named the many as one, which every author had to
335
336
  * read past. Only `affiliation` declares the field, so the alias is reported
@@ -13,6 +13,15 @@
13
13
  * @returns {Set<string>} The known action names.
14
14
  */
15
15
  export function collectKnownActionNames(repoRoot: string): Set<string>;
16
+ /**
17
+ * Scenes pack compiler.
18
+ *
19
+ * Walks the content tree and compiles every map note into one Scene, resolving
20
+ * what one note says about another through an index built before any scene is
21
+ * written. It also writes one Adventure per place, bundling those scenes with
22
+ * the JournalEntries their prose compiled into, which is what makes a pinned
23
+ * scene's id-based references resolve on import.
24
+ */
16
25
  export class Scenes extends BasePackCompiler {
17
26
  constructor({ contentBase, dest, skipDirectories, companionDests, folderResolver, repoRoot, }: {
18
27
  contentBase: any;
@@ -31,7 +40,7 @@ export class Scenes extends BasePackCompiler {
31
40
  */
32
41
  adventureCount: number;
33
42
  index: Map<string, object> | undefined;
34
- effectsByAddress: Map<any, any> | undefined;
43
+ effectsByAddress: Map<string, object> | undefined;
35
44
  knownActions: Set<string> | undefined;
36
45
  /** place key → `{name, img, scenes: [], journal: []}` */
37
46
  places: Map<any, any> | undefined;
@@ -46,7 +46,7 @@ export function declaredFields(artifact: SchemaArtifact, documentType: string, s
46
46
  * the path beneath it separately, so a comparison that knew only the leaf would
47
47
  * report the container as unemitted and the leaf as undeclared.
48
48
  *
49
- * **A runtime-only field is not in it** (#330). It declares a `to` in order to
49
+ * **A runtime-only field is not in it**. It declares a `to` in order to
50
50
  * *claim* the path — so the verbatim passthrough leaves it alone and the
51
51
  * refusal has something to name — and `buildFromFields` deliberately skips it,
52
52
  * because the document writes that field in play. Counting it here would make
@@ -76,7 +76,7 @@ export function emittedFields(fields: readonly {
76
76
  * compile into.
77
77
  * @param {(type: string) => string} [opts.subtypeOf] - Maps a builder's type to
78
78
  * the document subtype it emits. Defaults to identity, which is what the
79
- * coincidence of names amounts to today (#79) — stated as a seam so that the
79
+ * coincidence of names amounts to today — stated as a seam so that the
80
80
  * explicit map replaces a default rather than a hard-coded assumption.
81
81
  * @returns {{undeclared: object[], unemitted: object[], skipped: string[]}}
82
82
  * `undeclared` fails a build; `unemitted` is reported; `skipped` names the
@@ -96,7 +96,7 @@ export function compareFields({ builders, artifact, documentType, subtypeOf, }:
96
96
  };
97
97
  /**
98
98
  * What a **compiled document** carries in `system`, against what the receiving
99
- * subtype declares (#155).
99
+ * subtype declares.
100
100
  *
101
101
  * The third of the three checks, and the only one whose emitted set is
102
102
  * *observed*. {@link compareFields} reads the `itemBuilders` declarations and
@@ -148,7 +148,7 @@ export function compareEmittedSystem({ system, artifact, documentType, subtype,
148
148
  * The published schema this build should check itself against, or `null`.
149
149
  *
150
150
  * **Which system, and which version, are already settled.** `stats.systemId`
151
- * and `stats.systemVersion` are derived rather than authored (#48) — a system
151
+ * and `stats.systemVersion` are derived rather than authored — a system
152
152
  * package is its own system, and a module takes the one it requires — and the
153
153
  * version is the `compatibility.verified` it pins. So the question "whose
154
154
  * schema, at what version" has one answer here rather than a second set of
@@ -168,12 +168,12 @@ export function compareEmittedSystem({ system, artifact, documentType, subtype,
168
168
  * stamps no system at all, and a system that has not adopted the artifact yet
169
169
  * is simply unchecked. Neither is an error, and the caller says which it was.
170
170
  *
171
- * **A build may have more than one system, and then the caller names it (#139).**
171
+ * **A build may have more than one system, and then the caller names it.**
172
172
  * `stats.systemId` is the package-wide answer, and a repository shipping content
173
173
  * for two systems has no package-wide answer — it is deliberately `null` there,
174
174
  * because a module feeding both `sohl` and `hm3` targets neither. Left at that,
175
175
  * every schema check in such a build would be skipped in silence, which is the
176
- * state #60 exists to remove: the five type names the two systems *share* are
176
+ * state this exists to remove: the five type names the two systems *share* are
177
177
  * exactly the ones a wrong-system emission hides in. So a pass supplies the
178
178
  * system its pack declares, and the version comes from that system's own
179
179
  * `systems:` entry rather than from a package-wide stamp.
@@ -236,7 +236,7 @@ export function unemittedMessage(finding: object): string;
236
236
  export function emittedUndeclaredMessage(finding: EmissionFinding): string;
237
237
  /**
238
238
  * What a note authors under `<system>.system`, against what the receiving
239
- * subtype declares (#58).
239
+ * subtype declares.
240
240
  *
241
241
  * The **note-side** half of the check `compareFields` performs on the
242
242
  * declarations. A field list is checked once for the whole build because it is
@@ -261,7 +261,7 @@ export function emittedUndeclaredMessage(finding: EmissionFinding): string;
261
261
  * @param {string} opts.documentType - `Item`, `Actor`, …
262
262
  * @param {string} opts.subType - The document subtype the note compiles into.
263
263
  * @param {string} [opts.system] - The system whose published schema to read,
264
- * where a build has more than one (#139). Defaults to the package-wide
264
+ * where a build has more than one. Defaults to the package-wide
265
265
  * `stats.systemId`.
266
266
  * @param {object} [opts.config] - The resolved build configuration.
267
267
  * @returns {{path: string, message: string}[]} One finding per undeclared path,
@@ -279,7 +279,7 @@ export function checkAuthoredSystemData(fm: object, { block, documentType, subTy
279
279
  }[];
280
280
  /**
281
281
  * The `system` block a compiler just assembled, against what the receiving
282
- * subtype declares (#155).
282
+ * subtype declares.
283
283
  *
284
284
  * The build-time face of {@link compareEmittedSystem}: it resolves the schema
285
285
  * the way every other check here does — the system's own committed artifact, or
@@ -291,7 +291,7 @@ export function checkAuthoredSystemData(fm: object, { block, documentType, subTy
291
291
  * existed, or a subtype the artifact does not name, produces no findings.
292
292
  * `content-build lint` is where a missing artifact is said out loud, once.
293
293
  *
294
- * @param {object} system - The `system` block the compiler produced.
294
+ * @param {object} emitted - The `system` block the compiler produced.
295
295
  * @param {object} opts
296
296
  * @param {object} opts.fm - The note's frontmatter, for the authored paths this
297
297
  * check leaves to {@link checkAuthoredSystemData}.
@@ -302,12 +302,12 @@ export function checkAuthoredSystemData(fm: object, { block, documentType, subTy
302
302
  * @param {readonly {to?: string}[]} [opts.fields] - The type's field
303
303
  * declaration, which decides each finding's origin.
304
304
  * @param {string} [opts.system] - The system whose published schema to read,
305
- * where a build has more than one (#139). Defaults to the package-wide
305
+ * where a build has more than one. Defaults to the package-wide
306
306
  * `stats.systemId`.
307
307
  * @param {object} [opts.config] - The resolved build configuration.
308
308
  * @returns {(EmissionFinding & {message: string})[]} One per undeclared path.
309
309
  */
310
- export function checkEmittedSystemData(emitted: any, { fm, block, documentType, subType, type, fields, system, config, }: {
310
+ export function checkEmittedSystemData(emitted: object, { fm, block, documentType, subType, type, fields, system, config, }: {
311
311
  fm: object;
312
312
  block: string;
313
313
  documentType: string;
@@ -326,7 +326,7 @@ export function checkEmittedSystemData(emitted: any, { fm, block, documentType,
326
326
  *
327
327
  * A mismatch stops the check rather than resolving anyway: a schema read under
328
328
  * the wrong shape would report confident nonsense in both directions, and a
329
- * silently skipped check is the state #60 exists to leave.
329
+ * silently skipped check is the state this exists to leave.
330
330
  *
331
331
  * @type {number}
332
332
  */
@@ -5,7 +5,7 @@
5
5
  * tree in reverse. Order was load-bearing here when the address index carried
6
6
  * first-writer-wins fallbacks for a page's name, filename and slug — reversing
7
7
  * the walk silently changed which page an ambiguous name resolved to. Those
8
- * fallbacks are gone with the bare `[[Name]]` form (#180), so this is now
8
+ * fallbacks are gone with the bare `[[Name]]` form, so this is now
9
9
  * ordinary reading order rather than a dependency; it is kept because a site's
10
10
  * emitted pages should not reorder for no reason.
11
11
  *
@@ -51,13 +51,13 @@ export function collectTreePages(tree: object, ctx: object): {
51
51
  * it, because in homepage-only mode it is the **whole** of the site build: the
52
52
  * content tree is never read for pages at all, so the licensing constraint two
53
53
  * packages ship under is a property of the code path rather than of a
54
- * configuration that happens to be empty (#55).
54
+ * configuration that happens to be empty.
55
55
  *
56
56
  * Returned as a list rather than as the one note there should be, because the
57
- * count is what {@link checkHomepageCount} judges (#52) — this walk reports
57
+ * count is what {@link checkHomepageCount} judges — this walk reports
58
58
  * what it found, and {@link buildSite} decides whether that is one.
59
59
  *
60
- * A homepage that declares no `shortcode` has no address (#182), and is
60
+ * A homepage that declares no `shortcode` has no address, and is
61
61
  * reported rather than written: it is the same finding a content page's missing
62
62
  * shortcode produces, and it has to be available in homepage-only mode, where
63
63
  * no other gate runs.
@@ -85,13 +85,13 @@ export function collectHomepages(contentBase: string, ctx: object): {
85
85
  * for, and would make homepage-only mode depend on the index, the foreign
86
86
  * manifests and the table universe that mode exists to not build.
87
87
  *
88
- * **Verbatim is the answer to #54, not a gap left by it.** A landing's links
88
+ * **Verbatim is the answer, not a gap.** A landing's links
89
89
  * could not be *resolved* here without giving `homepage` mode the index its
90
90
  * licensing fence exists to not build, so they are **checked** instead:
91
91
  * {@link auditHomepageLinks} reads the `landing:` addresses and the body's
92
92
  * markdown links, and reports a wikilink on the page rather than resolving one.
93
93
  *
94
- * **Its destination is no longer fixed** (#182). The file is written at the
94
+ * **Its destination is no longer fixed**. The file is written at the
95
95
  * note's address, flat at the package's site root, and the page states that
96
96
  * address as its `url` — the same separation of file from URL every other page
97
97
  * has. Nothing is written at `/<package>/` itself: that becomes a redirect the
@@ -117,8 +117,7 @@ export function writeHomepages(outRoot: string, pages: readonly object[], config
117
117
  * - **Addresses** next: a note that has no address — no shortcode to be
118
118
  * addressed by, or no section to be filed under — would silently drop a page.
119
119
  * There is no collision gate beside it: an address is `(type, shortcode)`,
120
- * which is unique within a package by rule, so two pages cannot claim one URL
121
- * (#181).
120
+ * which is unique within a package by rule, so two pages cannot claim one URL.
122
121
  * - **Foreign manifests** last, in two steps. *Unusable* is a file this build
123
122
  * cannot read; *unaddressable* is one it can read but cannot look anything up
124
123
  * in — a distinction worth keeping, because the second surfaces as a pile of
@@ -127,10 +126,13 @@ export function writeHomepages(outRoot: string, pages: readonly object[], config
127
126
  * @param {object[]} pages - Every page, from both walks.
128
127
  * @param {object} findings - `{ addressFindings, fmLinkFindings }` from
129
128
  * collection.
130
- * @param {object} options - `{ config }`.
129
+ * @param {object} options
130
+ * @param {object} options.config - The resolved build configuration.
131
131
  * @returns {object} The gate results and, when they pass, the built index.
132
132
  */
133
- export function siteGates(pages: object[], findings: object, { config }: object): object;
133
+ export function siteGates(pages: object[], findings: object, { config }: {
134
+ config: object;
135
+ }): object;
134
136
  /**
135
137
  * The gate result of a build that ran none of them.
136
138
  *
@@ -167,7 +169,7 @@ export function tableUniverse(pages: object[]): Map<string, object[]>;
167
169
  * a value YAML can carry, and would abort the serializer.
168
170
  *
169
171
  * Everything else the section declared is passed through. That is the point of
170
- * the function: before #91 both writers transcribed `title` and `banner` by
172
+ * the function: two writers transcribing `title` and `banner` by
171
173
  * name, so the vocabulary lived in three places — the schema that admits a key
172
174
  * and the two writers that copy it — and a key added to the schema alone
173
175
  * validated cleanly and then reached no page. The *schema* is the bound worth
@@ -182,7 +184,7 @@ export function sectionFrontmatter(meta: object): object;
182
184
  /**
183
185
  * The frontmatter a page publishes with.
184
186
  *
185
- * An authored `aliases` is retired (#180) and refused before a build reaches
187
+ * An authored `aliases` is retired and refused before a build reaches
186
188
  * here, which makes this a guard rather than a working path. It was Obsidian's
187
189
  * — a list of *names* a reader might call
188
190
  * the note, which is vault addressing and stays in the vault. Hugo reads
@@ -191,13 +193,13 @@ export function sectionFrontmatter(meta: object): object;
191
193
  * redirects of its own.
192
194
  *
193
195
  * A content page states its own **`url`**, which is its address rather than its
194
- * path (#181). It is written flat under the content mount (#204), so Hugo would
196
+ * path. It is written flat under the content mount, so Hugo would
195
197
  * otherwise publish it at `<mount><type>-<shortcode>/` rather than at the
196
198
  * package-wide address the link manifest records — the same address, one
197
199
  * segment too deep. So the address is stated and the mount does not reach it.
198
200
  *
199
- * **It is stated relative to the site root, and so carries no package base**
200
- * (#217). Hugo resolves a `url` against `baseURL`, whose path is already where
201
+ * **It is stated relative to the site root, and so carries no package base**.
202
+ * Hugo resolves a `url` against `baseURL`, whose path is already where
201
203
  * the package is served — a consumer's Hugo site *is* its package — so writing
202
204
  * `page.url`, which carries the base for every href this build renders, wrote
203
205
  * that base a second time and published every content page a segment too deep
@@ -206,8 +208,8 @@ export function sectionFrontmatter(meta: object): object;
206
208
  * index a wikilink resolves through, and the link manifest — composes
207
209
  * `<base><slug>/`.
208
210
  *
209
- * A content page carries the package the build **derived** (#65). No note
210
- * declares one — `package:` is retired (#56) — so the note's frontmatter alone
211
+ * A content page carries the package the build **derived**. No note
212
+ * declares one — `package:` is retired — so the note's frontmatter alone
211
213
  * would publish a page that does not say which package it belongs to. The
212
214
  * emitted page is what a
213
215
  * theme reads: `breadcrumbs.html` builds its middle crumb from
@@ -221,16 +223,24 @@ export function sectionFrontmatter(meta: object): object;
221
223
  * section's landing and takes the title and hero the section declares.
222
224
  *
223
225
  * @param {object} page - The page.
224
- * @param {object} options - `{ readmeSections, decorate }`.
226
+ * @param {object} options
227
+ * @param {Record<string, object>} [options.readmeSections] - The sections a
228
+ * published tree declares, which a tree page's own `README` is the landing
229
+ * for.
230
+ * @param {(data: object, page: object) => void} [options.decorate] - Called
231
+ * with each page's frontmatter, for whatever a consumer's own pass adds.
225
232
  * @returns {object} The frontmatter to write.
226
233
  */
227
- export function pageFrontmatter(page: object, { readmeSections, decorate }: object): object;
234
+ export function pageFrontmatter(page: object, { readmeSections, decorate }: {
235
+ readmeSections?: Record<string, object> | undefined;
236
+ decorate?: ((data: object, page: object) => void) | undefined;
237
+ }): object;
228
238
  /**
229
239
  * Where a page is written, relative to the output root.
230
240
  *
231
- * **Flat, under the mount, named by its address** (#204). A content page's URL
241
+ * **Flat, under the mount, named by its address**. A content page's URL
232
242
  * is its address — `/<package>/<type>-<shortcode>/` — and the file is now named
233
- * the same way, so the two agree. It used to be filed into `<section>/` so that
243
+ * the same way, so the two agree. Filing it into `<section>/` so that
234
244
  * Hugo would read a section off its path; a section appears in no address, and
235
245
  * a directory chosen only to satisfy a rendering engine's idea of what a
236
246
  * section is has no business in the note format.
@@ -238,7 +248,7 @@ export function pageFrontmatter(page: object, { readmeSections, decorate }: obje
238
248
  * The name is the *whole* address rather than a section-relative half of it, so
239
249
  * two types cannot fight over one file: a `doc` note's `subType` may be spelled
240
250
  * the same as another note's `type`, and `doc-gear.md` and `weapongear-gear.md`
241
- * are distinct whatever the sections used to be.
251
+ * are distinct whatever the sections.
242
252
  *
243
253
  * **A `trees` entry is the exception, and always was.** Those pages preserve
244
254
  * their source layout below a named section — they are a book with chapters,
@@ -274,7 +284,7 @@ export function renderPages(pages: object[], options: object): {
274
284
  /**
275
285
  * Writes the Hugo sections a published tree declares.
276
286
  *
277
- * **This is where a section lives now, and the only place** (#204). A content
287
+ * **This is where a section lives now, and the only place**. A content
278
288
  * note carries none: it is addressed by `(type, shortcode)` and emitted flat
279
289
  * under the mount, so nothing a page does creates a directory. A site that wants
280
290
  * `/<package>/<prefix><section>/` to answer — with a title, a hero, and whatever
@@ -311,10 +321,21 @@ export function renderPages(pages: object[], options: object): {
311
321
  * navigation of every page inside it.
312
322
  *
313
323
  * @param {string} outRoot - The mount directory.
314
- * @param {object} options - `{ sections, landing, sectionTitle }`.
324
+ * @param {object} options
325
+ * @param {Record<string, object>} [options.sections] - The declared sections,
326
+ * each written as a titled `_index.md` carrying its own frontmatter.
327
+ * @param {object} [options.landing] - The mount's own landing frontmatter.
328
+ * Omitted, the mount gets no `_index.md` of its own.
329
+ * @param {((name: string) => string)|null} [options.sectionTitle] - Titles a
330
+ * directory below the mount that declared no section. `null` leaves such a
331
+ * directory without an `_index.md`.
315
332
  * @returns {number} How many landings were written.
316
333
  */
317
- export function writeSectionLandings(outRoot: string, { sections, landing, sectionTitle }: object): number;
334
+ export function writeSectionLandings(outRoot: string, { sections, landing, sectionTitle }: {
335
+ sections?: Record<string, object> | undefined;
336
+ landing?: object | undefined;
337
+ sectionTitle?: ((name: string) => string) | null | undefined;
338
+ }): number;
318
339
  /**
319
340
  * A section landing's title, from its directory name — `macro` → `Macros`.
320
341
  *
@@ -371,12 +392,18 @@ export function resolveOutputRoot(rootDir: string, out: string): string;
371
392
  * @param {object} [options.config] - A resolved configuration; loaded when
372
393
  * omitted.
373
394
  * @param {string} [options.outRoot] - Override the configured output mount.
395
+ * @param {Map<string, object[]>} [options.sqlTables] - Prepared `sql` results,
396
+ * keyed by the note's absolute file, from
397
+ * {@link module:engine/sql-tables.prepareSqlTables}. A page authoring an
398
+ * `sql` directive with none prepared is a table error: nothing here runs a
399
+ * query.
374
400
  * @returns {{gates: object, stats: object|null, tableErrors: object[],
375
401
  * wikiErrors: object[], manifests: object|null}}
376
402
  */
377
403
  export function buildSite({ config, outRoot, sqlTables }?: {
378
404
  config?: object | undefined;
379
405
  outRoot?: string | undefined;
406
+ sqlTables?: Map<string, object[]> | undefined;
380
407
  }): {
381
408
  gates: object;
382
409
  stats: object | null;
@@ -38,7 +38,7 @@ export function buildSiteIndex(entries: readonly SiteEntry[], { foreignIndex }?:
38
38
  *
39
39
  * There is deliberately **no `manifestsComplete`**. It used to let a resolver
40
40
  * soften an unresolved cross-package address while any package's manifest was
41
- * missing; #184 retired the softening, since the pack compilers and the link
41
+ * missing; the softening is retired, since the pack compilers and the link
42
42
  * checker never had it and one authored link must not get two verdicts. A
43
43
  * caller still passing it is ignored rather than obeyed.
44
44
  */
@@ -76,8 +76,7 @@ export type SiteEntry = {
76
76
  * The Hugo section a **tree** page is filed under, and
77
77
  * the first segment of the `<sec>/<slug>` address it
78
78
  * is reachable by. A content page has none: it is
79
- * addressed by `(type, shortcode)` and emitted flat
80
- * (#204).
79
+ * addressed by `(type, shortcode)` and emitted flat.
81
80
  */
82
81
  sec?: string | undefined;
83
82
  /**
@@ -101,7 +100,7 @@ export type SiteIndex = {
101
100
  /**
102
101
  * Address → page. `draft` says the page
103
102
  * carries the `draft` tag, which marks a
104
- * link *into* it (#183).
103
+ * link *into* it.
105
104
  */
106
105
  index: Map<string, {
107
106
  url: string;