@heroiclands/package-build 23.7.1 → 23.9.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 (96) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +32 -0
  3. package/bin/content-commands.mjs +32 -10
  4. package/ci/check-banners.mjs +37 -19
  5. package/container.mjs +48 -10
  6. package/docs/api.md +402 -58
  7. package/docs/authoring/first-note.md +1 -1
  8. package/docs/authoring/frontmatter.md +30 -1
  9. package/docs/authoring/links-and-markup.md +154 -56
  10. package/docs/commands.md +31 -12
  11. package/docs/content-format.md +25 -2
  12. package/docs/design.md +31 -0
  13. package/docs/diagnostics.md +1 -1
  14. package/docs/guides/e2e.md +37 -1
  15. package/docs/guides/packs.md +2 -1
  16. package/docs/guides/reference-pages.md +86 -63
  17. package/docs/project-setup.md +30 -15
  18. package/docs/reference/format-details.md +313 -132
  19. package/docs/reference/note-types.md +191 -174
  20. package/e2e.mjs +89 -2
  21. package/engine/anchors.mjs +7 -20
  22. package/engine/base-compiler.mjs +11 -18
  23. package/engine/compile-corpus.mjs +5 -3
  24. package/engine/content-alerts.mjs +4 -2
  25. package/engine/content-blocks.mjs +18 -36
  26. package/engine/content-format.yaml +1 -0
  27. package/engine/content-index.mjs +447 -33
  28. package/engine/content-links.mjs +43 -15
  29. package/engine/content-poetry.mjs +9 -0
  30. package/engine/content-tables.mjs +8 -4
  31. package/engine/culture-reach.mjs +61 -34
  32. package/engine/data-keys.mjs +3 -1
  33. package/engine/diagnostics.mjs +24 -11
  34. package/engine/dictionary-layout.mjs +151 -19
  35. package/engine/dictionary-rows.mjs +129 -79
  36. package/engine/foundry-entries.mjs +21 -3
  37. package/engine/frontmatter-lint.mjs +1 -1
  38. package/engine/generate.mjs +12 -22
  39. package/engine/glossary-entries.mjs +11 -11
  40. package/engine/heading-attributes.mjs +21 -28
  41. package/engine/helpers.mjs +20 -12
  42. package/engine/infobox.mjs +3 -0
  43. package/engine/journals.mjs +15 -2
  44. package/engine/language-data.mjs +53 -0
  45. package/engine/language-terms.mjs +86 -0
  46. package/engine/language-words.mjs +2 -2
  47. package/engine/lexical-sections.mjs +247 -0
  48. package/engine/markdownlint-run.mjs +12 -2
  49. package/engine/markup-validation.mjs +34 -0
  50. package/engine/note-addresses.mjs +1 -0
  51. package/engine/note-claims.mjs +21 -10
  52. package/engine/note-package.mjs +3 -5
  53. package/engine/note-state.mjs +9 -3
  54. package/engine/note-vocabulary.mjs +36 -7
  55. package/engine/pdf-build.mjs +17 -10
  56. package/engine/prose-lint.mjs +12 -6
  57. package/engine/related-pages.mjs +22 -3
  58. package/engine/see-also.mjs +150 -0
  59. package/engine/site-build.mjs +21 -3
  60. package/engine/site-preview.mjs +4 -2
  61. package/engine/sql-tables.mjs +287 -53
  62. package/engine/standings.mjs +14 -12
  63. package/engine/stub-lint.mjs +6 -2
  64. package/engine/web-wikilinks.mjs +8 -6
  65. package/engine/word-rows.mjs +20 -12
  66. package/engine/word-terms.mjs +15 -6
  67. package/hugo-theme/layouts/partials/related.html +25 -8
  68. package/package.json +2 -1
  69. package/types/container.d.mts +4 -3
  70. package/types/e2e.d.mts +28 -0
  71. package/types/engine/base-compiler.d.mts +2 -2
  72. package/types/engine/compile-corpus.d.mts +2 -1
  73. package/types/engine/content-index.d.mts +85 -8
  74. package/types/engine/content-tables.d.mts +2 -1
  75. package/types/engine/culture-reach.d.mts +22 -2
  76. package/types/engine/data-keys.d.mts +7 -0
  77. package/types/engine/diagnostics.d.mts +7 -6
  78. package/types/engine/dictionary-layout.d.mts +35 -4
  79. package/types/engine/dictionary-rows.d.mts +9 -0
  80. package/types/engine/foundry-entries.d.mts +3 -1
  81. package/types/engine/heading-attributes.d.mts +2 -2
  82. package/types/engine/helpers.d.mts +11 -5
  83. package/types/engine/language-terms.d.mts +7 -0
  84. package/types/engine/lexical-sections.d.mts +95 -0
  85. package/types/engine/markdownlint-run.d.mts +3 -1
  86. package/types/engine/markup-validation.d.mts +14 -0
  87. package/types/engine/note-addresses.d.mts +10 -0
  88. package/types/engine/note-claims.d.mts +7 -1
  89. package/types/engine/note-package.d.mts +3 -1
  90. package/types/engine/note-state.d.mts +2 -1
  91. package/types/engine/note-vocabulary.d.mts +1 -0
  92. package/types/engine/related-pages.d.mts +10 -1
  93. package/types/engine/see-also.d.mts +55 -0
  94. package/types/engine/site-build.d.mts +4 -1
  95. package/types/engine/sql-tables.d.mts +40 -6
  96. package/types/engine/word-rows.d.mts +9 -7
@@ -62,10 +62,10 @@ takes.
62
62
 
63
63
  `data.pack: none` is appropriate when the page belongs on the site and in the
64
64
  index but does not need a Foundry compendium entry. A documentation package
65
- has no packs at all. A page with an empty body is a stub: it remains in the
66
- index for data references, but publishes no page address or anchors. Write a
67
- description for a stub and leave off the `draft` tag; use `draft` for a page
68
- with prose still in progress. The [stub rules](../reference/format-details.md#a-stub-is-a-note-with-an-empty-body)
65
+ has no packs at all. A page with neither an authored body nor automatic
66
+ Dictionary or Glossary content is a stub: it remains in the index for data
67
+ references, but publishes no page address or anchors. Supply content before
68
+ publishing; use `draft` for prose still in progress. The [stub rules](../reference/format-details.md#a-stub-is-a-note-with-an-empty-body)
69
69
  and [pack routing](packs.md) cover these cases.
70
70
 
71
71
  ## Link to the intended target
@@ -165,92 +165,115 @@ reconstructing them from filenames.
165
165
 
166
166
  ## Set words as a dictionary and terms as a glossary
167
167
 
168
- A language's words and a people's terms are each one `sql` fence, and nothing
169
- is copied into the note: the words come from the language notes' `data.words`,
170
- the terms from the `data.glossary` entries of cultures and affiliations and
171
- from the affiliations' ranks and offices, and a section changes when they do.
172
- `layout=dictionary` sets rows of the `dictionary` relation and
173
- `layout=glossary` rows of the `terms` relation. The query decides what
174
- appears, so any note may carry either.
168
+ A language note's `data.words` supplies its Dictionary section automatically
169
+ on the site, in the Foundry journal and in the book. The dictionary is the
170
+ language's full word list, including inherited words, in one alphabetical
171
+ sequence with letter headings, field tags and one key. A language with absent
172
+ or empty `data.words` receives no Dictionary section.
175
173
 
176
- A language note lists its words, with the words it inherits from its parent
177
- language:
174
+ The frontmatter states the words:
178
175
 
179
- ````markdown
180
- # Dictionary {#dictionary}
181
-
182
- ```sql {layout=dictionary section-level=2}
183
- SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-provnzlng'
176
+ ```yaml
177
+ shortcode: provnzlng
178
+ name: { full: Provenzal Language }
179
+ type: skill
180
+ subType: language
181
+ data:
182
+ classModifiers: [f, m]
183
+ fields:
184
+ - { name: Love and its conduct, holds: "Longing, courtship and the courts of love." }
185
+ words:
186
+ - {
187
+ form: solmère,
188
+ class: n m,
189
+ pronunciation: sol-MEH-reh,
190
+ field: Love and its conduct,
191
+ gloss: "longing; the ache for a person, a place or a time that is gone",
192
+ }
193
+ - {
194
+ form: amère,
195
+ class: n f,
196
+ field: Love and its conduct,
197
+ gloss: ["a beloved", "a sweetheart, in the songs"],
198
+ }
184
199
  ```
185
- ````
186
200
 
187
- A culture note lists the words of its tongue a player is expected to know, and
188
- its glossary: its own terms and the ranks, offices and terms of every
189
- affiliation that reaches it.
201
+ A culture or affiliation's `data.glossary` supplies its Glossary section. It
202
+ contains only that owner's declared entries, so each culture and affiliation
203
+ using a language chooses its own common words and context meanings. Ranks
204
+ and offices stand in the affiliation's own sections; an explicit glossary
205
+ entry can name one, but a glossary does not draw in the whole ladder or list
206
+ of offices. A note with absent or empty `data.glossary` receives no Glossary
207
+ section.
190
208
 
191
- ````markdown
192
- # Words Everyone Knows {#common-words}
209
+ The [worked culture glossary](../reference/format-details.md#glossary-terms)
210
+ uses all three reasons for an entry:
193
211
 
194
- ```sql {layout=dictionary section-level=2}
195
- SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-provnzlng' AND common
212
+ ```yaml
213
+ data:
214
+ language: skill-provnzlng
215
+ glossary:
216
+ - { name: solmère, common: true }
217
+ - { name: amère, definition: "a sweetheart, in the songs" }
218
+ - {
219
+ name: blood-field,
220
+ definition: "ground where a battle tore the boundary with the spirit world, and it did not close",
221
+ }
196
222
  ```
197
223
 
198
- # Glossary {#glossary}
199
-
200
- ```sql {layout=glossary section-level=2}
201
- SELECT * FROM terms WHERE list_contains(cultures, 'thalorna-note-lore-provenzianclt')
202
- ```
203
- ````
224
+ `solmère` is part of this culture's core vocabulary: the words a player needs
225
+ to adventure there. With no `definition`, it carries the senses and class of
226
+ the dictionary word and links to that word's entry. `amère` supplies a context
227
+ meaning, which takes the place of its dictionary senses while retaining the
228
+ word link and class. `blood-field` supplies a gloss for a term outside the
229
+ dictionary. Common marks belong to the glossary's owner; dictionaries mark
230
+ nothing common. There are no setting-wide dictionary or glossary pages.
204
231
 
205
- The glossary renders as a key, a heading per letter and one paragraph per
206
- term, each sense tagged with the body or culture it comes from and each term
207
- in another tongue tagged with it:
232
+ The generated sections follow the note's authored text. A heading with
233
+ `{#dictionary}` or `{#lexicon}` receives the Dictionary list, and a heading
234
+ with `{#glossary}` receives the Glossary list, after the authored text in
235
+ that section. This preserves an introduction or loanword discussion under
236
+ an anchored heading:
208
237
 
209
238
  ```markdown
210
- **Key:** _Lit._ Liturgic Language; • a common word, which a player is expected to know by heart.
239
+ # Lexicon {#lexicon}
211
240
 
212
- ## K
213
-
214
- **Knight-Captain** **1** (_Kingdom of Provenzia_) A knight who leads a company. **2** (_Kingdom of Provenzia_) Commands the guard in the field. — [[thalorna-note-affiliation-kingdom|Kingdom of Provenzia]]
215
-
216
- ## O
217
-
218
- **Ordained Priest** _Lit._ (_Church of the Rite_) Ordained to serve a parish. — [[thalorna-note-affiliation-church|Church of the Rite]]
241
+ The tongue of Provenzia, whose _solmère_ is its own word.
219
242
  ```
220
243
 
221
- The culture shapes both through its own `data.glossary`.
222
- `{name: solmère, common: true, pronunciation: sol-MEH-reh}` marks a word of its
223
- tongue common, so the first fence lists it, and gives it a respelling;
224
- `{name: Subject, common: true}` marks a rank and lets in a title of one word,
225
- which no glossary holds unless an entry names it; and
226
- `{name: blood-field, definition: …}` is a term of the culture's own.
244
+ The list is generated once under that heading. Without such a heading, the
245
+ build supplies **Dictionary** or **Glossary** at the end of the note.
227
246
 
228
- An affiliation lists its own terms with `WHERE owner = '<its Address>'`. The
229
- setting's two pages list everything, each word once and each term under every
230
- owner that holds it:
247
+ ### Custom views
231
248
 
232
- ````markdown
233
- ```sql {layout=dictionary}
234
- SELECT * EXCLUDE (common) FROM dictionary WHERE depth = 0
235
- ```
236
- ````
249
+ An `sql` fence with `layout=dictionary` or `layout=glossary` can add a custom
250
+ view of the same `dictionary` and `terms` relations. This one selects only a
251
+ culture's common entries:
237
252
 
238
253
  ````markdown
239
- ```sql {layout=glossary}
240
- SELECT * EXCLUDE (common) FROM terms
254
+ # Words Everyone Knows {#common-words}
255
+
256
+ ```sql {layout=glossary section-level=2}
257
+ SELECT * FROM terms WHERE owner = 'thalorna-note-lore-provenzianclt' AND common
241
258
  ```
242
259
  ````
243
260
 
244
- `package-build content-index` also writes both relations beside the index, for
245
- reading with `jq`:
261
+ The `terms` relation also holds ranks and offices for custom queries. The
262
+ default Glossary section selects only rows arising from its owner's explicit
263
+ `data.glossary` entries, including an entry naming a rank or office. The
264
+ [layout guide](../authoring/links-and-markup.md#dictionaries-and-glossaries)
265
+ describes query options and rendering.
266
+
267
+ `package-build content-index` writes both relations beside the index. To
268
+ read a culture's own entries with `jq`:
246
269
 
247
270
  ```bash
248
- jq -r 'select(.cultures | index("thalorna-note-lore-provenzianclt")) | .name' \
271
+ jq -r 'select(.owner == "thalorna-note-lore-provenzianclt") | .name' \
249
272
  build/content-index/thalorna-terms.jsonl
250
273
  ```
251
274
 
252
275
  The [format reference](../reference/format-details.md#dictionaries-and-glossaries)
253
- gives every column and every rule.
276
+ gives every column and rule.
254
277
 
255
278
  ## Resolve another package
256
279
 
@@ -78,24 +78,39 @@ who knows the groups can guess a name.
78
78
  ### `prepare` — the git hooks
79
79
 
80
80
  ```json
81
- "prepare": "git config core.hooksPath node_modules/@heroiclands/package-build/githooks || true"
81
+ "prepare": "git config core.hooksPath .githooks || true"
82
82
  ```
83
83
 
84
- npm runs `prepare` after every install, so this is what installs the hooks. It
85
- points git at the hook directory this package ships, which means a repository
86
- carries no hook files of its own and cannot drift from the others. `|| true` so
87
- that an install outside a git checkout — a Docker build, a CI cache step —
88
- does not fail on it.
84
+ npm runs `prepare` after every install. In this repository it points Git at the
85
+ committed `.githooks/`, including in a fresh worktree. Run `npm ci` for a fresh
86
+ checkout or `npm run prepare` to repeat setup. `|| true` allows an install
87
+ outside a Git checkout, such as a Docker build or CI cache step.
89
88
 
90
- Four hooks arrive with it, each with its own switch, read with git's normal
89
+ The installed `commit-msg` and `pre-commit` are byte-identical copies of the
90
+ [canonical POSIX hooks](https://github.com/HeroicLands/.github#local-git-hooks-and-actionsgithooks).
91
+ The message guard refuses assistant credits without requiring Node; the branch
92
+ guard refuses commits on `main` and `master`. These hooks honor
93
+ `hooks.noAttribution`, `hooks.protectedBranch`, and `hooks.allowCommitOnMain`.
94
+ CI compares every installed hook against the pinned canonical set. This setup
95
+ installs no pre-push or pre-merge-commit hook.
96
+
97
+ ### Optional published hook bundle
98
+
99
+ The package also ships `githooks/` for repositories that explicitly select its
100
+ additional checks. To select that bundle, point `core.hooksPath` at
101
+ `node_modules/@heroiclands/package-build/githooks` in that repository’s setup.
102
+ That bundle requires the installed package and Node; it is separate from this
103
+ repository’s canonical `.githooks/` installation.
104
+
105
+ Four hooks are available, each with its own switch, read with Git’s normal
91
106
  precedence: a plain `git config` sets one clone, `--global` sets a machine.
92
107
 
93
- | Hook | What it does | Key | Default |
94
- | -------------------------------- | --------------------------------------------------------------- | ------------------------------------ | ------- |
95
- | `pre-commit`, `pre-merge-commit` | Refuse a commit on a protected branch. | `hooks.allowCommitOnMain` (inverted) | on |
96
- | `pre-commit` | Lint any `.changeset/*.md` the commit stages. | `hooks.changelogCheck` | on |
97
- | `commit-msg` | Refuse AI attribution in a commit message. | `hooks.noAttribution` | on |
98
- | `pre-push` | Run this repository's own Build & Test workflow in a container. | `hooks.prePushCi` | off |
108
+ | Hook | What it does | Key | Default |
109
+ | -------------------------------- | -------------------------------------------------------------------- | ------------------------------------ | ------- |
110
+ | `pre-commit`, `pre-merge-commit` | Refuse a commit on a protected branch. | `hooks.allowCommitOnMain` (inverted) | on |
111
+ | `pre-commit` | Lint any `.changeset/*.md` the commit stages. | `hooks.changelogCheck` | on |
112
+ | `commit-msg` | Refuse AI attribution in a commit message. | `hooks.noAttribution` | on |
113
+ | `pre-push` | Run the consuming repository’s Build & Test workflow in a container. | `hooks.prePushCi` | off |
99
114
 
100
115
  The defaults differ deliberately: a guard that costs nothing is on unless
101
116
  refused, and one that runs a container for minutes is off unless asked for.
@@ -120,8 +135,8 @@ Git comment lines and the verbose diff after a scissors comment are excluded.
120
135
  Findings report the original message line and column. This local hook examines
121
136
  commit messages only; the shared No Attribution workflow also checks pull
122
137
  request titles and bodies. Neither examines repository files, issues, comments,
123
- or Git author/committer metadata. The package-build repository's own hook uses
124
- the same matching code and remains enabled independently of the consumer switch.
138
+ or Git author/committer metadata. The canonical shell hooks apply the same
139
+ credit grammar with their own POSIX implementation.
125
140
 
126
141
  The branch guard is the one you meet first:
127
142