@heroiclands/package-build 0.6.1 → 3.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.
- package/CHANGELOG-content-build.md +965 -0
- package/CHANGELOG.md +58 -0
- package/CONTENT.md +824 -0
- package/MIGRATING.md +127 -0
- package/README.md +29 -17
- package/bin/content-build.mjs +1082 -0
- package/bin/package-build.mjs +2 -2
- package/bin/report.mjs +2 -2
- package/config.mjs +21 -18
- package/content-config.mjs +1424 -0
- package/coverage.mjs +1 -1
- package/e2e.mjs +1 -1
- package/engine/abbreviations.mjs +0 -0
- package/engine/base-compiler.mjs +608 -0
- package/engine/code-fences.mjs +311 -0
- package/engine/compendiums.mjs +353 -0
- package/engine/content-address.mjs +141 -0
- package/engine/content-links.mjs +438 -0
- package/engine/content-lint.mjs +204 -0
- package/engine/content-package.mjs +67 -0
- package/engine/content-slug.mjs +169 -0
- package/engine/content-tables.mjs +1216 -0
- package/engine/content-tree.mjs +56 -0
- package/engine/diagnostics.mjs +270 -0
- package/engine/field-reference.mjs +242 -0
- package/engine/field-spec.mjs +218 -0
- package/engine/foreign-catalog.mjs +450 -0
- package/engine/foreign-manifests.mjs +129 -0
- package/engine/frontmatter-lint.mjs +343 -0
- package/engine/frontmatter.mjs +258 -0
- package/engine/generate.mjs +305 -0
- package/engine/helpers.mjs +809 -0
- package/engine/ids.mjs +192 -0
- package/engine/index.mjs +142 -0
- package/engine/item-docs.mjs +153 -0
- package/engine/item-registry.mjs +129 -0
- package/engine/journals.mjs +374 -0
- package/engine/kb-manifest.mjs +473 -0
- package/engine/macros.mjs +333 -0
- package/engine/manifest-emit.mjs +339 -0
- package/engine/map-notes.mjs +1302 -0
- package/engine/pack-config.mjs +479 -0
- package/engine/pack-router.mjs +254 -0
- package/engine/prose-config.mjs +238 -0
- package/engine/prose-lint.mjs +294 -0
- package/engine/region-events.mjs +79 -0
- package/engine/scene-levels.mjs +420 -0
- package/engine/scenes.mjs +528 -0
- package/engine/site-build.mjs +778 -0
- package/engine/site-index.mjs +332 -0
- package/engine/web-wikilinks.mjs +330 -0
- package/engine/wikilink-syntax.mjs +105 -0
- package/engine/wikilinks.mjs +645 -0
- package/index.mjs +42 -25
- package/lang.mjs +1 -1
- package/manifest.mjs +79 -5
- package/markdownlint-config.mjs +45 -0
- package/package.json +78 -35
- package/prettier-config.mjs +34 -0
- package/sohl/actors.mjs +581 -0
- package/sohl/affiliation-standings.mjs +44 -0
- package/sohl/being-info.mjs +173 -0
- package/sohl/default-item-art.mjs +82 -0
- package/sohl/index.mjs +55 -0
- package/sohl/item-builders.mjs +114 -0
- package/sohl/item-fields.mjs +867 -0
- package/sohl/items.mjs +197 -0
- package/sohl/kb-passes.mjs +207 -0
- package/sohl/note-schemas.mjs +322 -0
- package/templates.mjs +1 -1
- package/types/config.d.mts +1 -1
- package/types/content-config.d.mts +571 -0
- package/types/engine/abbreviations.d.mts +44 -0
- package/types/engine/base-compiler.d.mts +320 -0
- package/types/engine/code-fences.d.mts +102 -0
- package/types/engine/compendiums.d.mts +77 -0
- package/types/engine/content-address.d.mts +61 -0
- package/types/engine/content-links.d.mts +87 -0
- package/types/engine/content-lint.d.mts +47 -0
- package/types/engine/content-package.d.mts +36 -0
- package/types/engine/content-slug.d.mts +59 -0
- package/types/engine/content-tables.d.mts +131 -0
- package/types/engine/content-tree.d.mts +12 -0
- package/types/engine/diagnostics.d.mts +152 -0
- package/types/engine/field-reference.d.mts +20 -0
- package/types/engine/field-spec.d.mts +181 -0
- package/types/engine/foreign-catalog.d.mts +103 -0
- package/types/engine/foreign-manifests.d.mts +43 -0
- package/types/engine/frontmatter-lint.d.mts +74 -0
- package/types/engine/frontmatter.d.mts +99 -0
- package/types/engine/generate.d.mts +56 -0
- package/types/engine/helpers.d.mts +348 -0
- package/types/engine/ids.d.mts +126 -0
- package/types/engine/index.d.mts +33 -0
- package/types/engine/item-docs.d.mts +73 -0
- package/types/engine/item-registry.d.mts +57 -0
- package/types/engine/journals.d.mts +121 -0
- package/types/engine/kb-manifest.d.mts +214 -0
- package/types/engine/macros.d.mts +201 -0
- package/types/engine/manifest-emit.d.mts +137 -0
- package/types/engine/map-notes.d.mts +283 -0
- package/types/engine/pack-config.d.mts +77 -0
- package/types/engine/pack-router.d.mts +53 -0
- package/types/engine/prose-config.d.mts +121 -0
- package/types/engine/prose-lint.d.mts +59 -0
- package/types/engine/region-events.d.mts +43 -0
- package/types/engine/scene-levels.d.mts +36 -0
- package/types/engine/scenes.d.mts +48 -0
- package/types/engine/site-build.d.mts +214 -0
- package/types/engine/site-index.d.mts +141 -0
- package/types/engine/web-wikilinks.d.mts +67 -0
- package/types/engine/wikilink-syntax.d.mts +101 -0
- package/types/engine/wikilinks.d.mts +172 -0
- package/types/index.d.mts +3 -0
- package/types/manifest.d.mts +52 -4
- package/types/markdownlint-config.d.mts +8 -0
- package/types/prettier-config.d.mts +1 -0
- package/types/sohl/actors.d.mts +65 -0
- package/types/sohl/affiliation-standings.d.mts +22 -0
- package/types/sohl/being-info.d.mts +78 -0
- package/types/sohl/default-item-art.d.mts +28 -0
- package/types/sohl/index.d.mts +7 -0
- package/types/sohl/item-builders.d.mts +11 -0
- package/types/sohl/item-fields.d.mts +20 -0
- package/types/sohl/items.d.mts +19 -0
- package/types/sohl/kb-passes.d.mts +71 -0
- package/types/sohl/note-schemas.d.mts +7 -0
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
# @heroiclands/content-build
|
|
2
|
+
|
|
3
|
+
## 1.8.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1fb6b96: Move the release workflow to `changesets/action@v2`.
|
|
8
|
+
|
|
9
|
+
v2 renamed four of the inputs this workflow passes — `version` →
|
|
10
|
+
`version-script`, `publish` → `publish-script`, `commit` → `commit-message`,
|
|
11
|
+
`title` → `pr-title` — and **rejects the old names outright** rather than
|
|
12
|
+
warning and carrying on. So the bump and the rename have to land in the same
|
|
13
|
+
commit.
|
|
14
|
+
|
|
15
|
+
Taken deliberately rather than waiting for Dependabot, because Dependabot bumps
|
|
16
|
+
the pin without touching the inputs, and that combination has already broken the
|
|
17
|
+
release pipeline in three sibling repositories:
|
|
18
|
+
Song-of-Heroic-Lands-FoundryVTT#1729, sohl-thalorna#71 and
|
|
19
|
+
sohl-kethira-basic#42. In the first of those it went unnoticed for over two
|
|
20
|
+
weeks — a failing release job looks exactly like a repository nobody has
|
|
21
|
+
released lately.
|
|
22
|
+
|
|
23
|
+
Nothing else had to move: `version-script` already calls an npm script (#74), and
|
|
24
|
+
one command is what v2's tokenized, never-shelled input requires.
|
|
25
|
+
|
|
26
|
+
## 1.8.1
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- 9cbf2c4: Actually assign the foreign item catalogue onto the actors compiler.
|
|
31
|
+
|
|
32
|
+
1.8.0 shipped `itemCatalog: true` and `deps fetch` in a state where they did
|
|
33
|
+
nothing. The compiler destructured `foreignSourceDirs` and never assigned it, so
|
|
34
|
+
`this.foreignSourceDirs` was always `undefined` and `loadItemsMap` fell back to
|
|
35
|
+
its empty default. The catalogue was downloaded, extracted, cached — and
|
|
36
|
+
silently dropped.
|
|
37
|
+
|
|
38
|
+
The symptom was a compile that had changed in no way at all: `sohl-thalorna`
|
|
39
|
+
with the feature fully switched on still reported all 26,220 unresolved items,
|
|
40
|
+
and logged `Loaded 630 predefined items` — its own count, with none of sohl's
|
|
41
|
+
1,224.
|
|
42
|
+
|
|
43
|
+
Every test passed throughout, because they exercised the catalogue module in
|
|
44
|
+
isolation and nothing exercised the wiring. A feature wired up wrong looks
|
|
45
|
+
exactly like one switched off, so the regression test asserts the compiler keeps
|
|
46
|
+
the directories it was constructed with, and was checked by mutation.
|
|
47
|
+
|
|
48
|
+
## 1.8.0
|
|
49
|
+
|
|
50
|
+
### Minor Changes
|
|
51
|
+
|
|
52
|
+
- 0bf5334: Resolve a being's embedded items against a dependency's shipped item catalogue.
|
|
53
|
+
|
|
54
|
+
A repository that authors beings without holding the items they are assembled
|
|
55
|
+
from could not compile an actors pack at all: the pass resolved against local
|
|
56
|
+
Item packs and nothing else, so `sohl-thalorna` turning its actors pack on
|
|
57
|
+
produced 26,220 unresolved-item errors.
|
|
58
|
+
|
|
59
|
+
A declared relationship may now opt in with `itemCatalog: true`. Fetching
|
|
60
|
+
downloads that package's release, extracts its Item packs with
|
|
61
|
+
`@foundryvtt/foundryvtt-cli`, and hands the resulting directories to the actors
|
|
62
|
+
pass, which reads them exactly as it reads a local pack.
|
|
63
|
+
|
|
64
|
+
Three things it does deliberately:
|
|
65
|
+
|
|
66
|
+
- **Pins, rather than following `latest`.** A published
|
|
67
|
+
`releases/latest/download/…` URL is rewritten to the declared
|
|
68
|
+
`compatibility.verified` version, so a build names one particular dependency
|
|
69
|
+
and stays reproducible. Where the URL cannot be rewritten, the version that
|
|
70
|
+
comes back is checked against `verified` instead — floating silently is not
|
|
71
|
+
on offer.
|
|
72
|
+
- **Never reaches the network during a compile.** Fetching is its own command,
|
|
73
|
+
`content-build deps fetch`, and a compile with a cold cache fails naming it.
|
|
74
|
+
The cache is version-keyed, so a second run costs nothing.
|
|
75
|
+
- **Lets a local item shadow a foreign one.** Two local packs claiming one
|
|
76
|
+
address still collide, because that is ambiguous; a repository's own
|
|
77
|
+
`skill:awar` standing in front of the system's is not.
|
|
78
|
+
|
|
79
|
+
New `paths.foreignCache` (`build/cache/foreign`) names where catalogues land.
|
|
80
|
+
|
|
81
|
+
## 1.7.0
|
|
82
|
+
|
|
83
|
+
### Minor Changes
|
|
84
|
+
|
|
85
|
+
- 189b795: Retire the address-alias rule (#79).
|
|
86
|
+
|
|
87
|
+
`lint` required every note to repeat its own `type-shortcode` address in the
|
|
88
|
+
top-level `aliases:` list. That served exactly one reader — **Obsidian**, so
|
|
89
|
+
`[[type-shortcode]]` resolved in the editor — and no build ever read it: both
|
|
90
|
+
resolvers parse the hyphen qualifier themselves, and the alias list feeds only
|
|
91
|
+
the bare-alias fallback index.
|
|
92
|
+
|
|
93
|
+
The project no longer authors in Obsidian, so the rule required a line of
|
|
94
|
+
frontmatter per note for a reader that does not exist.
|
|
95
|
+
|
|
96
|
+
`sohl-thalorna` had already dropped its aliases, which left its `lint` reporting
|
|
97
|
+
**1,738 findings — one per note**, none of them a defect, burying the 120 that
|
|
98
|
+
were real. With the rule retired that tree reports **0** address findings, and
|
|
99
|
+
`sohl` (1,457 notes) and `sohl-kethira-basic` (363 notes) — which still carry
|
|
100
|
+
their aliases — report 0 as well. An alias that is still there is simply an
|
|
101
|
+
ordinary alias now.
|
|
102
|
+
|
|
103
|
+
`isAddressAlias` and `auditNoteAliases` are removed with it. Neither was
|
|
104
|
+
imported by anything but this package's own tests.
|
|
105
|
+
|
|
106
|
+
_Verified output-neutral before the aliases were dropped:_ across 1,735 stripped
|
|
107
|
+
notes, `package compile` produced byte-identical `build/packs-json` and the site
|
|
108
|
+
build byte-identical `site/content`. Only `lint` ever disagreed.
|
|
109
|
+
|
|
110
|
+
The two rules that remain are the ones about identity rather than tooling: a
|
|
111
|
+
`shortcode` is ASCII-alphanumeric, and `(type, shortcode)` names one note.
|
|
112
|
+
|
|
113
|
+
## 1.6.0
|
|
114
|
+
|
|
115
|
+
### Minor Changes
|
|
116
|
+
|
|
117
|
+
- be6667e: Apply the shared markdown indentation in a repository that declares no Prettier
|
|
118
|
+
config of its own (#76).
|
|
119
|
+
|
|
120
|
+
`content-build format` fell back to the shared configuration object and passed
|
|
121
|
+
it inline to Prettier. **Prettier applies an `overrides` block only while
|
|
122
|
+
resolving a config file**, never to options handed to it directly, so the
|
|
123
|
+
`**/*.md → tabWidth: 2` override was silently dropped and markdown was formatted
|
|
124
|
+
at the global `tabWidth: 4`.
|
|
125
|
+
|
|
126
|
+
Pointing `resolveConfig` at the shipped config file does not fix it either:
|
|
127
|
+
Prettier matches an override's glob relative to **the config file's own
|
|
128
|
+
directory**, and that file lives inside `node_modules`.
|
|
129
|
+
|
|
130
|
+
**Only a consumer with no Prettier config was affected** — which is every
|
|
131
|
+
repository this command was added for. One with a config resolves it from its
|
|
132
|
+
own root and was always correct, which is why this repository and
|
|
133
|
+
`Song-of-Heroic-Lands-FoundryVTT` both reported clean.
|
|
134
|
+
|
|
135
|
+
Caught while adopting the command in `sohl-thalorna`: it proposed rewriting
|
|
136
|
+
**1,738 content notes**, converting YAML frontmatter from 2-space to 4-space.
|
|
137
|
+
SoHL's notes are 2-space, so that is precisely backwards for a configuration
|
|
138
|
+
whose purpose is that a note formatted in one repository is formatted the same
|
|
139
|
+
way in the next. Verified after the fix: a real thalorna note is now returned
|
|
140
|
+
byte-identical, and the tree reports zero markdown findings.
|
|
141
|
+
|
|
142
|
+
The values are now declared once as `PRETTIER_BASE` + `PRETTIER_MARKDOWN`, with
|
|
143
|
+
`PRETTIER_CONFIG` composing them into the shape a config file wants and the new
|
|
144
|
+
`sharedPrettierOptionsFor(file)` giving the runner the same values as flat
|
|
145
|
+
options. One source, two presentations, and nothing passes `overrides` inline
|
|
146
|
+
again.
|
|
147
|
+
|
|
148
|
+
## 1.5.0
|
|
149
|
+
|
|
150
|
+
### Minor Changes
|
|
151
|
+
|
|
152
|
+
- 4a86493: Carry a mystical ability's and a mystery's granting affiliation through to the
|
|
153
|
+
compiled document (#3).
|
|
154
|
+
|
|
155
|
+
`assocAffiliationCode` is a real field on both `MysticalAbilityDataModel` and
|
|
156
|
+
`MysteryDataModel`, and neither type's declaration named it — so the builders,
|
|
157
|
+
which are an allow-list, discarded it. In `sohl-kethira-basic` **204 of 224
|
|
158
|
+
mysticalability notes set it to a real value**, and every one of them compiled
|
|
159
|
+
without it: no mystical ability in that shipped pack was linked to the
|
|
160
|
+
affiliation that grants it. `mystery` was missing `assocSkillCode` for the same
|
|
161
|
+
reason.
|
|
162
|
+
|
|
163
|
+
Both are declared `nullable: true, blank: false, initial: null` on their
|
|
164
|
+
DataModels, so the new fields read blank as `null` rather than `""` — "unset" is
|
|
165
|
+
one value, not two.
|
|
166
|
+
|
|
167
|
+
**This changes emitted documents**, so a consumer whose notes set either field
|
|
168
|
+
wants a rebuild rather than a silent upgrade.
|
|
169
|
+
|
|
170
|
+
_Not fixed here:_ the silence itself. A `sohl:` key no declaration names is
|
|
171
|
+
still dropped with no warning and no effect on the exit code, which is what made
|
|
172
|
+
this cost 204 documents before anyone noticed — that is #19's unknown-property
|
|
173
|
+
check. The inverse case, an emitted field no DataModel declares, is #70.
|
|
174
|
+
|
|
175
|
+
- d9ea689: Check a note's frontmatter against the schema its type declares (#19), and make
|
|
176
|
+
the builders' allow-list loud (#3).
|
|
177
|
+
|
|
178
|
+
`content-build lint` now checks frontmatter as well as addresses. Five classes,
|
|
179
|
+
each previously reported somewhere other than where it was made, or not at all:
|
|
180
|
+
|
|
181
|
+
- **Unknown or retired type**, told what replaced it.
|
|
182
|
+
- **Missing required property** — `dimensions` on a map, `subType` on a skill.
|
|
183
|
+
- **Wrong value shape** — `weight: heavy` where a number belongs.
|
|
184
|
+
- **Unknown property**, with a near-miss suggestion. This is #3's second half:
|
|
185
|
+
the builders discard a `sohl:` key no field declares, with no warning and no
|
|
186
|
+
effect on the exit code, which is how 204 kethira mystical abilities shipped
|
|
187
|
+
with no affiliation. An author could not tell a builder that forgot a field
|
|
188
|
+
apart from a field that does not belong on the type at all.
|
|
189
|
+
- **Dead shortcode reference**, resolved through the same resolver `links` uses,
|
|
190
|
+
so a cross-package reference answered by a vendored manifest lands exactly as
|
|
191
|
+
it would in a wikilink. `--no-references` turns it off for a tree whose
|
|
192
|
+
cross-package references it cannot see.
|
|
193
|
+
|
|
194
|
+
**A schema says what a note may _write_, not what the compiler emits.** That
|
|
195
|
+
distinction is the calibration: a note also feeds a knowledgebase and a website,
|
|
196
|
+
which read classification the pack build never compiles. Equating the vocabulary
|
|
197
|
+
with the builder's allow-list reported 4,241 unknown properties against SoHL's
|
|
198
|
+
own tree, every one correctly authored; declared properly, the same tree reports
|
|
199
|
+
**nothing** across 1,457 notes.
|
|
200
|
+
|
|
201
|
+
What that calibration then finds elsewhere is real: 120 findings in
|
|
202
|
+
`sohl-thalorna` — including 44 mysteries still carrying the retired `trait`, a
|
|
203
|
+
being on the retired `birthsign`, and a skill with no `subType` — and 270 in
|
|
204
|
+
`sohl-kethira-basic`.
|
|
205
|
+
|
|
206
|
+
**Expect a previously green tree to go red.** That is the point of the issue,
|
|
207
|
+
not a regression: the findings were always there and nothing reported them.
|
|
208
|
+
|
|
209
|
+
Two additions to a field declaration make this checkable: `kind`, a
|
|
210
|
+
machine-readable value shape distinct from the prose `shape` (a field may
|
|
211
|
+
declare one without changing a byte of what it emits), and `ref`, the content
|
|
212
|
+
type a shortcode addresses.
|
|
213
|
+
|
|
214
|
+
- 97fcb9b: Own prose formatting and markdown linting, so a consumer invokes rather than
|
|
215
|
+
configures (#69).
|
|
216
|
+
|
|
217
|
+
Two new commands:
|
|
218
|
+
|
|
219
|
+
- `content-build format [paths..] [--write]` — Prettier, with the shared
|
|
220
|
+
configuration.
|
|
221
|
+
- `content-build markdown [paths..] [--fix]` — markdownlint, with a narrow,
|
|
222
|
+
individually justified rule set covering the structure Prettier is indifferent
|
|
223
|
+
to: a skipped heading level, two sibling headings claiming one anchor, a
|
|
224
|
+
reversed `(text)[url]`, a bare URL, an empty link, a table row with the wrong
|
|
225
|
+
cell count, and the emphasis markers these repositories write.
|
|
226
|
+
|
|
227
|
+
**Why here.** Nothing checked the _shape_ of the markdown this package compiles
|
|
228
|
+
— `lint` checks addresses, `links` checks that links land. Each consumer wired
|
|
229
|
+
prose checking itself, so coverage was lopsided: SoHL ran both tools, thalorna
|
|
230
|
+
had Prettier but never from `lint`, and kethira had neither, leaving the package
|
|
231
|
+
least likely to have been proofread checked for addresses and nothing else. This
|
|
232
|
+
package is the only one all three consume.
|
|
233
|
+
|
|
234
|
+
**Both are defaults, not overrides.** A consumer's own Prettier config or
|
|
235
|
+
`.markdownlint-cli2.jsonc` wins. Repository-layout knowledge — which paths to
|
|
236
|
+
skip — stays in that repository's `.prettierignore` and `.gitignore`, both
|
|
237
|
+
honoured natively. `CHANGELOG.md` is skipped by default, since `changeset
|
|
238
|
+
version` regenerates it in every repository here.
|
|
239
|
+
|
|
240
|
+
Neither tool's file discovery is reimplemented, so `content-build format` and a
|
|
241
|
+
bare `prettier --check .` report the same thing — verified against SoHL's tree
|
|
242
|
+
(2,470 files, identical result). A file Prettier cannot parse is reported as a
|
|
243
|
+
located finding rather than taking the run down, which is how
|
|
244
|
+
`sohl-kethira-basic`'s invalid `lang/en.json` was found
|
|
245
|
+
(HeroicLands/sohl-kethira-basic#34).
|
|
246
|
+
|
|
247
|
+
The shared rules are also exported for editors, so format-on-save agrees with
|
|
248
|
+
the lint chain: `@heroiclands/content-build/prettier` and
|
|
249
|
+
`@heroiclands/content-build/markdownlint`.
|
|
250
|
+
|
|
251
|
+
_New runtime dependencies:_ `markdownlint-cli2`, and `prettier` moves from a dev
|
|
252
|
+
dependency to a real one — the commands run them in process.
|
|
253
|
+
|
|
254
|
+
## 1.4.0
|
|
255
|
+
|
|
256
|
+
### Minor Changes
|
|
257
|
+
|
|
258
|
+
- 04577c7: **`content-build manifest` emits a package's link manifest, so no consumer
|
|
259
|
+
writes the walk itself (#58).**
|
|
260
|
+
|
|
261
|
+
`writeManifests` could always write a manifest; nothing could _derive_ one. So a
|
|
262
|
+
repository that publishes one wrote the walk, the address derivation, the anchor
|
|
263
|
+
pass and the entry assembly for itself — 285 lines in `sohl`, 300 in
|
|
264
|
+
`sohl-thalorna` — and the two drifted in ways nobody chose. One routed its UUIDs
|
|
265
|
+
through the pack router and one did not, so a repository shipping several packs
|
|
266
|
+
of a type published UUIDs naming the wrong one.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
npx content-build manifest # the configured tree and output directory
|
|
270
|
+
npx content-build manifest --out tmp/ # or somewhere else
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
It takes no paths. The content tree, the output directory, the two package
|
|
274
|
+
identities and the address scheme all come from configuration; `[root]` and
|
|
275
|
+
`--out` exist to point the same derivation at a scratch tree.
|
|
276
|
+
|
|
277
|
+
**The base a manifest records against is gone from the interface, because it was
|
|
278
|
+
never an input.** Both scripts built a site-absolute URL and handed
|
|
279
|
+
`buildManifest` the base it was built from, whose first act is to strip that same
|
|
280
|
+
prefix back off — the value provably never reached the file. Addresses are now
|
|
281
|
+
derived package-relative from the start. What survives is the two-state
|
|
282
|
+
distinction `publish.site` already carries: a build that publishes no pages emits
|
|
283
|
+
entries with no `path`, exactly as a note that compiles into no document emits
|
|
284
|
+
none with no `uuid`.
|
|
285
|
+
|
|
286
|
+
**What genuinely differed between the two consumers is now one setting, shared
|
|
287
|
+
with the page build.** Where the content tree mounts inside the package, and
|
|
288
|
+
which note addresses a whole section rather than a page within one, are both
|
|
289
|
+
load-bearing — `sohl` records `kb/affliction/aconite/` and `thalorna` records
|
|
290
|
+
`affiliation/the-aerarium-imperii/` — and reading them in one place is what stops
|
|
291
|
+
a manifest asserting an address the site does not publish:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
publish:
|
|
295
|
+
site: true
|
|
296
|
+
manifests: { publish: true, consume: true }
|
|
297
|
+
address:
|
|
298
|
+
prefix: kb/ # default "" — the content tree mounts at the package root
|
|
299
|
+
landing: readme # readme | collection
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`landing` names which note is a section's landing page: `readme` (a `README.md`
|
|
303
|
+
addresses its section) or `collection` (a `doc` note whose `category` is
|
|
304
|
+
`collection` addresses the section it introduces, named by its authored
|
|
305
|
+
`section`). The two are alternatives, not a pair that could both apply — each
|
|
306
|
+
live content tree holds notes the other rule would move.
|
|
307
|
+
|
|
308
|
+
**Verified byte-for-byte against both consumers**: the command reproduces
|
|
309
|
+
`sohl`'s manifest (2,691 entries from 1,457 notes) and `sohl-thalorna`'s (2,367
|
|
310
|
+
entries) exactly as the scripts it replaces emit them, on the same toolchain.
|
|
311
|
+
|
|
312
|
+
Also new:
|
|
313
|
+
|
|
314
|
+
- `paths.manifestOut` (default `build/manifests`) — where the manifest is
|
|
315
|
+
written. Deliberately not `paths.manifests`, which is the _inbound_ directory
|
|
316
|
+
of vendored foreign manifests that `links` consumes.
|
|
317
|
+
- `publish.manifests.publish` is enforced as a declaration rather than a
|
|
318
|
+
preference: with it off, emitting fails instead of writing a file other
|
|
319
|
+
repositories would vendor and read as authoritative. The check lives in the
|
|
320
|
+
library, so a caller that bypasses the command cannot bypass the declaration.
|
|
321
|
+
- A note the scheme yields no address for is reported as a located diagnostic and
|
|
322
|
+
omitted, never guessed — the old scripts printed a loose list.
|
|
323
|
+
- `engine/manifest-emit.mjs` exports the pass (`collectManifestEntries`,
|
|
324
|
+
`entriesForNote`, `anchorsOf`, `emitLinkManifest`) for a consumer that needs a
|
|
325
|
+
step of it rather than the whole command.
|
|
326
|
+
|
|
327
|
+
- 7ce0349: **`content-build site` publishes a content tree as a website, so no consumer
|
|
328
|
+
writes the pipeline itself (#63).**
|
|
329
|
+
|
|
330
|
+
Compiling a content tree into compendium packs was `content-build package
|
|
331
|
+
compile`. Publishing the _same tree_ as a website was a script each consumer
|
|
332
|
+
wrote for itself — 473 code lines in `sohl`, 462 in `sohl-thalorna`, 87 of them
|
|
333
|
+
identical — and the copies drifted where nobody could see it. `sohl-thalorna`
|
|
334
|
+
reimplemented four things this package already exported, not because it needed
|
|
335
|
+
different behaviour but because its script predates the extraction.
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npx content-build site # the configured tree and output
|
|
339
|
+
npx content-build site --out tmp/kb # or somewhere else
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The command does the walk, the frontmatter read, the address derivation, the
|
|
343
|
+
address index, table expansion, wikilink resolution, code-fence protection, the
|
|
344
|
+
foreign-manifest merge, page emission, and the section-landing backfill.
|
|
345
|
+
|
|
346
|
+
**Addresses are not part of the new `site:` section.** They come from
|
|
347
|
+
`publish.address`, the same setting `manifest` reads, so a page and its manifest
|
|
348
|
+
entry cannot disagree about where the page is. `site:` is framing only — the
|
|
349
|
+
output root, the base, which packages are rendered, what a section is called,
|
|
350
|
+
which extra trees are published beside the content, and which named pass bundle
|
|
351
|
+
supplies the repository's own body rewrites.
|
|
352
|
+
|
|
353
|
+
**Consumer passes are named, not imported.** A repository's own rewrites are
|
|
354
|
+
code and a configuration is data, so a configuration names a bundle and the
|
|
355
|
+
toolchain resolves it, exactly as `itemBuilders` names an item registry.
|
|
356
|
+
`sohlKb` is the bundle for the `sohl` knowledgebase — `{@link}` against a
|
|
357
|
+
TypeDoc symbol map, and repository-relative links in developer docs. A bundle
|
|
358
|
+
supplies `beforeLinks` (every page, before wikilinks resolve) and `afterLinks`
|
|
359
|
+
(extra-tree pages only), both inside code-fence protection.
|
|
360
|
+
|
|
361
|
+
**Every gate reports; none exits.** The seven integrity checks — a wikilink in
|
|
362
|
+
frontmatter, a name yielding no slug, two notes claiming one URL, an unusable or
|
|
363
|
+
unaddressable vendored manifest, an address two packages both claim, a bad table
|
|
364
|
+
or dead link — were inline `process.exit` calls in both scripts, with no test
|
|
365
|
+
between them. They now return findings and the command decides, which is the
|
|
366
|
+
only reason they can be tested at all.
|
|
367
|
+
|
|
368
|
+
**Verified byte-for-byte**: the command reproduces `sohl`'s entire published tree
|
|
369
|
+
— 1,520 files, 5,479,528 bytes — exactly as the script it replaces emits it.
|
|
370
|
+
|
|
371
|
+
**A safety note worth stating plainly.** The output tree is wiped on every run so
|
|
372
|
+
a renamed note's page cannot linger. An unset `site.out` resolves to the
|
|
373
|
+
repository root, and the wipe then deletes the working tree — which happened
|
|
374
|
+
while this command was being written, on a configuration that had no `site`
|
|
375
|
+
section yet. `site.out` is now required, and refused again unless it resolves
|
|
376
|
+
strictly inside the repository root. Both failing shapes are ordinary rather than
|
|
377
|
+
exotic, so neither is left to care.
|
|
378
|
+
|
|
379
|
+
Also new:
|
|
380
|
+
|
|
381
|
+
- `engine/site-build.mjs` exports each stage (`collectContentPages`,
|
|
382
|
+
`collectTreePages`, `siteGates`, `renderPages`, `writeSectionLandings`) for a
|
|
383
|
+
consumer that needs a step rather than the whole command.
|
|
384
|
+
- `sohl/kb-passes.mjs` exports the two `sohl` rewrites directly.
|
|
385
|
+
- `gray-matter` is a dependency. It is the authority on the exact bytes of a
|
|
386
|
+
page's frontmatter, and matching it is what makes the byte-identical claim
|
|
387
|
+
above true rather than approximately true.
|
|
388
|
+
|
|
389
|
+
## 1.3.0
|
|
390
|
+
|
|
391
|
+
### Minor Changes
|
|
392
|
+
|
|
393
|
+
- 1a36dfa: **`docs item-fields` renders the page a consumer publishes, not just the
|
|
394
|
+
tables.**
|
|
395
|
+
|
|
396
|
+
The command existed but no consumer could use it: it emitted the generated
|
|
397
|
+
tables and nothing else, so a repository that wanted a page — with a heading, a
|
|
398
|
+
"See also" line, and a paragraph telling the reader what they are looking at —
|
|
399
|
+
wrapped the renderer in a script of its own. That script was the thing the
|
|
400
|
+
command line exists to remove.
|
|
401
|
+
|
|
402
|
+
A new top-level `docs:` section says what is the consumer's:
|
|
403
|
+
|
|
404
|
+
```yaml
|
|
405
|
+
docs:
|
|
406
|
+
itemFields:
|
|
407
|
+
title: Item Note Frontmatter
|
|
408
|
+
out: kb/dev-docs/content-creator/item-frontmatter.md
|
|
409
|
+
preamble:
|
|
410
|
+
- "See also: [The Authoring Workflow](authoring-workflow.md)"
|
|
411
|
+
- ""
|
|
412
|
+
- Every item note carries the envelope described there.
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`--check` compares against the file already there and writes nothing, so a
|
|
416
|
+
repository can gate on the page being current without a temporary file or a
|
|
417
|
+
second implementation of the comparison.
|
|
418
|
+
|
|
419
|
+
**The page is now what Prettier would write.** A consumer commits it and formats
|
|
420
|
+
its repository, so a generator that disagreed with the formatter by one
|
|
421
|
+
character would have its output rewritten on the next format run and then called
|
|
422
|
+
stale by `--check` on every clean checkout — the two undoing each other forever.
|
|
423
|
+
Three things were making that happen, and all three are fixed at the source
|
|
424
|
+
rather than by adding a formatting pass:
|
|
425
|
+
|
|
426
|
+
- Table columns are padded to their widest cell, which is what Prettier's
|
|
427
|
+
alignment comes to for this content.
|
|
428
|
+
- The worked example's fence said `yaml`, but the block is a whole note —
|
|
429
|
+
frontmatter _and_ the prose beneath it. Prettier formats a fenced block in the
|
|
430
|
+
language it declares, so labelling it YAML both misdescribed it and dropped the
|
|
431
|
+
blank line after the frontmatter. It is `markdown`.
|
|
432
|
+
- One field description used `*emphasis*`; Prettier normalises to `_emphasis_`.
|
|
433
|
+
Fixed where it is written rather than by rewriting markers on the way out.
|
|
434
|
+
|
|
435
|
+
`tests/field-reference.test.ts` asserts the rendered page survives Prettier
|
|
436
|
+
unchanged, so if its markdown printer changes — or a field description starts
|
|
437
|
+
using a construct it normalises — that fails here, in the package that generates
|
|
438
|
+
the page, rather than in the repository that publishes it.
|
|
439
|
+
|
|
440
|
+
Also clears prose left behind when the manifest template was retired: the
|
|
441
|
+
`paths.packageManifest` typedefs, and a `config.mjs` example still showing a
|
|
442
|
+
`foundryPackage` that is now rejected.
|
|
443
|
+
|
|
444
|
+
## 1.2.0
|
|
445
|
+
|
|
446
|
+
### Minor Changes
|
|
447
|
+
|
|
448
|
+
- c5360c5: **Every invocation the command line accepts is now one it performs.**
|
|
449
|
+
|
|
450
|
+
Four invocations were accepted, performed nothing, and exited 0. From a `run-s`
|
|
451
|
+
build chain each read as a step that had done its work:
|
|
452
|
+
|
|
453
|
+
| Invocation | Was | Now |
|
|
454
|
+
| -------------------------- | -------------------------------------------- | ------------------------------ |
|
|
455
|
+
| `content-build` | exit 0, no output | usage, exit non-zero |
|
|
456
|
+
| `content-build bogus` | exit 0, silently ignored | rejected by name |
|
|
457
|
+
| `content-build package` | exit 0, compiled nothing | names `compile\|unpack\|clean` |
|
|
458
|
+
| `content-build docs` | rendered `item-fields` whatever it was asked | names the documents |
|
|
459
|
+
| `content-build lint --xyz` | exit 0, option ignored | rejected |
|
|
460
|
+
|
|
461
|
+
The CLI is built on yargs but had opted into none of its guarantees — no
|
|
462
|
+
`.demandCommand()`, no `.strict()`, and both multi-action commands declared
|
|
463
|
+
their action optional (`package [action]`) rather than required. `docs` went
|
|
464
|
+
further: it declared an `action` positional with `choices` and never read
|
|
465
|
+
`argv.action`, so the positional constrained what could be typed and selected
|
|
466
|
+
nothing. With one document that was latent; a second would have rendered the
|
|
467
|
+
wrong one and exited 0. The action is now dispatched on.
|
|
468
|
+
|
|
469
|
+
The sibling toolchain `@heroiclands/package-build` already opts into the same
|
|
470
|
+
two guards, so the two command lines now agree about what an error is.
|
|
471
|
+
|
|
472
|
+
**On the bump.** Marked _minor_ rather than _major_ although exit codes change
|
|
473
|
+
for inputs that were previously accepted: no invocation that did any work
|
|
474
|
+
behaves differently, and every invocation that changes was one doing nothing at
|
|
475
|
+
all. A consumer whose build starts failing was not compiling, linting or
|
|
476
|
+
rendering anything at that step. Treating it as a breaking change would strand
|
|
477
|
+
every `^1.0.0` consumer for a fix whose entire effect is to make a silent
|
|
478
|
+
no-op loud.
|
|
479
|
+
|
|
480
|
+
`--version` and `--help` still answer in a directory with no configuration.
|
|
481
|
+
|
|
482
|
+
Closes #57
|
|
483
|
+
|
|
484
|
+
## 1.1.0
|
|
485
|
+
|
|
486
|
+
### Minor Changes
|
|
487
|
+
|
|
488
|
+
- a032fd7: **The package-id guard is deleted, and with it every read of the shipped
|
|
489
|
+
manifest.** A single source needs no corroboration.
|
|
490
|
+
|
|
491
|
+
`assertPackageIdMatchesManifest` existed because the package id was declared
|
|
492
|
+
twice — once in configuration, once in a hand-authored manifest template — and
|
|
493
|
+
guarded the pair against drift (#1503). content-build 1.0.0 derived the
|
|
494
|
+
configured half from `package.json`; package-build 0.3.0 generates the manifest
|
|
495
|
+
from that same configuration. The guard was left in place through both, since
|
|
496
|
+
deleting it before the second declaration was actually gone would have removed a
|
|
497
|
+
check that still checked something. Both are gone now, and it compares a derived
|
|
498
|
+
value against itself.
|
|
499
|
+
|
|
500
|
+
Removed rather than repaired, along with everything that only existed to serve
|
|
501
|
+
it: `engine/package-manifest.mjs` entire — `resolvePackageManifestPath`,
|
|
502
|
+
`readPackageManifest`, `readManifestPackageId`,
|
|
503
|
+
`assertPackageIdMatchesManifestFile` — its barrel export, and the
|
|
504
|
+
`paths.packageManifest` key.
|
|
505
|
+
|
|
506
|
+
**`content-build package unpack` reads the configured pack list.** It took the
|
|
507
|
+
list out of the shipped manifest, which was the same second declaration one
|
|
508
|
+
level along. Nothing in the toolchain opens a manifest template now, so a
|
|
509
|
+
repository that has deleted `assets/templates/` compiles, unpacks and stamps
|
|
510
|
+
exactly as before.
|
|
511
|
+
|
|
512
|
+
**Breaking for any configuration still declaring `paths.packageManifest`** — the
|
|
513
|
+
key is refused, naming it. Every consumer drops it in the same change that
|
|
514
|
+
deletes its template.
|
|
515
|
+
|
|
516
|
+
## 1.0.0
|
|
517
|
+
|
|
518
|
+
### Major Changes
|
|
519
|
+
|
|
520
|
+
- 29857ed: **Four keys change hands: two stop being authored, two start.** All four were
|
|
521
|
+
wrong in the same way — a fact either transcribed into the configuration from a
|
|
522
|
+
file that already stated it, or read back _out of_ the manifest because the
|
|
523
|
+
configuration could not state it.
|
|
524
|
+
|
|
525
|
+
| Key | Was | Is |
|
|
526
|
+
| --------------------- | --------------------------------- | --------------------------------- |
|
|
527
|
+
| `foundryPackage` | transcribed `package.json` `name` | derived; authoring it is an error |
|
|
528
|
+
| `stats.systemVersion` | declarable | derived; authoring it is an error |
|
|
529
|
+
| `compatibility` | read out of the manifest | declared, top level |
|
|
530
|
+
| `relationships` | hand-authored in the manifest | declared, top level |
|
|
531
|
+
|
|
532
|
+
**Breaking.** Every consumer configuration must drop `foundryPackage` and
|
|
533
|
+
`stats.systemVersion` and gain `compatibility`, moving the values out of its
|
|
534
|
+
manifest template rather than retyping them.
|
|
535
|
+
|
|
536
|
+
**A module's system version is not its own version.** For a system,
|
|
537
|
+
`package.json` `version` _is_ the system version. For a module it is the
|
|
538
|
+
_module's_ — `sohl-thalorna` sits at `0.0.1` — so deriving from it would stamp a
|
|
539
|
+
SoHL version that has never existed, which is worse than the frozen `0.6.0` both
|
|
540
|
+
modules carry today, since that at least was once true. It comes instead from
|
|
541
|
+
the `compatibility.verified` of the system the module declares a relationship
|
|
542
|
+
with: `_stats.systemVersion` records what the packs were built against, not the
|
|
543
|
+
floor they tolerate. A module declaring no usable system relationship fails the
|
|
544
|
+
build rather than guessing.
|
|
545
|
+
|
|
546
|
+
**This reverses a rule.** Configuration used to be forbidden from holding the
|
|
547
|
+
Foundry floor — it named the manifest and the value was read from there, because
|
|
548
|
+
the manifest was hand-authored and moved with test evidence. Now that
|
|
549
|
+
package-build generates the manifest _from_ the configuration, reading it back
|
|
550
|
+
would be a round trip through an artifact that need not exist yet: `build:db`
|
|
551
|
+
can run before the manifest is written. `supportedCoreVersion` takes the
|
|
552
|
+
resolved configuration instead of a manifest directory, and no longer reads the
|
|
553
|
+
filesystem at all. The loud failure survives the reversal: an undeclared floor
|
|
554
|
+
throws rather than defaulting, which is what `coreVersion: "14"` taught (#1533).
|
|
555
|
+
|
|
556
|
+
`relationships` is **top level**, not in `packageBuild:`, because this package
|
|
557
|
+
must read the system relationship to derive a module's version — and the
|
|
558
|
+
dependency runs one way, so content-build must never read package-build's
|
|
559
|
+
section.
|
|
560
|
+
|
|
561
|
+
Mind the collision: top-level `compatibility` is the **Foundry core** range;
|
|
562
|
+
`relationships.systems[].compatibility` is the **game system's**. Same key,
|
|
563
|
+
different subject. `minimum` is required of the former, since it is stamped into
|
|
564
|
+
every document, and optional inside a relationship, where `verified` is what is
|
|
565
|
+
load-bearing.
|
|
566
|
+
|
|
567
|
+
The package-id drift guard is deliberately left in place. It compares this
|
|
568
|
+
configuration's id against the shipped template's, and the template still
|
|
569
|
+
declares one; it becomes vacuous only once package-build generates the manifest,
|
|
570
|
+
and should be deleted then rather than repaired.
|
|
571
|
+
|
|
572
|
+
## 0.17.0
|
|
573
|
+
|
|
574
|
+
### Minor Changes
|
|
575
|
+
|
|
576
|
+
- da18007: **The address index a site build resolves its wikilinks against moves here.**
|
|
577
|
+
|
|
578
|
+
`engine/site-index.mjs` exports `buildSiteIndex` and `wikiContext`: given pages
|
|
579
|
+
that already know their own URLs, it builds every key space a wikilink resolver
|
|
580
|
+
reads — `section/slug`, `type/shortcode`, the canonical
|
|
581
|
+
`package-type-shortcode`, collision-aware bare fallbacks, and type-scoped
|
|
582
|
+
aliases — merges the foreign packages in, and reports what more than one package
|
|
583
|
+
claims.
|
|
584
|
+
|
|
585
|
+
Every consumer publishing a content tree as a website answers the same question
|
|
586
|
+
— given `[[Something]]`, which page? — and each answered it with its own copy.
|
|
587
|
+
`sohl` and `sohl-thalorna` still share **147 identical lines** of that answer,
|
|
588
|
+
comments and indentation aside.
|
|
589
|
+
|
|
590
|
+
**What deliberately stays with the consumer:** how a page gets its address. The
|
|
591
|
+
URL scheme, the section a note is filed under, whether developer docs are part
|
|
592
|
+
of the site at all — the two builds differ on all three, and those differences
|
|
593
|
+
are real rather than drift.
|
|
594
|
+
|
|
595
|
+
The ordering of the foreign merge is now pinned by a test and explained where it
|
|
596
|
+
happens: foreign entries merge _before_ local canonical addresses are written,
|
|
597
|
+
so a local page always ends up owning its own `package-type-shortcode` even if a
|
|
598
|
+
stale vendored manifest claims it.
|
|
599
|
+
|
|
600
|
+
Additive — nothing here consumes it yet. Verified against SoHL's real tree:
|
|
601
|
+
1,457 notes and a 2,101-entry foreign index produce an index identical to the
|
|
602
|
+
one its build constructs today, across all six key spaces (12,663 addresses, 12
|
|
603
|
+
ambiguous keys, 3,402 type-scoped aliases, 3 poisoned aliases, 34 content types,
|
|
604
|
+
0 conflicts).
|
|
605
|
+
|
|
606
|
+
## 0.16.0
|
|
607
|
+
|
|
608
|
+
### Minor Changes
|
|
609
|
+
|
|
610
|
+
- db89a4d: **A being's info-block derivation moves here, from the two repositories that
|
|
611
|
+
each had a copy.**
|
|
612
|
+
|
|
613
|
+
`sohl/being-info.mjs` exports `deriveBeingInfo`, `isBeing`, `BEING_TYPE` and
|
|
614
|
+
`GEAR_TYPE_TO_KEY`: the translation between the flat `sohl.items[]` a being note
|
|
615
|
+
authors and the resolved shapes the shared theme's sidebar reads — a `skills`
|
|
616
|
+
map, `gear` grouped by kind, and `spells`/`talents` split out of the mystical
|
|
617
|
+
abilities. It is SoHL data-model knowledge (which item type is a skill, where a
|
|
618
|
+
mastery level lives, what separates a spell from a talent), so it belongs in
|
|
619
|
+
this package's `sohl` half rather than in each site build.
|
|
620
|
+
|
|
621
|
+
It lived in `Song-of-Heroic-Lands-FoundryVTT` and `sohl-thalorna` at once, and
|
|
622
|
+
the copies drifted. SoHL's caller still gated the derivation on `character` and
|
|
623
|
+
`creature` — the types #1580 merged into `being` — so it had matched nothing
|
|
624
|
+
since the merge, and all 95 of its being pages published with empty sidebar
|
|
625
|
+
sections (SoHL#1696). thalorna's copy checked `being` and was correct. Nothing
|
|
626
|
+
failed in either repository.
|
|
627
|
+
|
|
628
|
+
`isBeing` exists because of that: the defect was never in the derivation, it was
|
|
629
|
+
in each caller's idea of what a being _is_, written out per repository where it
|
|
630
|
+
could rot independently. The retired names are deliberately not accepted as
|
|
631
|
+
aliases — they throw elsewhere in the system, and tolerating them here would
|
|
632
|
+
hide the next drift instead of surfacing it.
|
|
633
|
+
|
|
634
|
+
Two deliberate differences from the code it replaces:
|
|
635
|
+
|
|
636
|
+
- **The `corpus` derivation is dropped.** `corpus` is not a registered item
|
|
637
|
+
type, so nothing can compile to one and the branch matched nothing — the same
|
|
638
|
+
class of dead code as the gate that caused the bug. It was in SoHL's copy and
|
|
639
|
+
never in thalorna's.
|
|
640
|
+
- The mystical-ability branch keeps its lack of a shortcode fallback, unlike
|
|
641
|
+
gear, and now says why: these render as prose names, so a row reading like a
|
|
642
|
+
shortcode is worse than no row.
|
|
643
|
+
|
|
644
|
+
Additive — nothing in this package consumes it yet. Verified against every real
|
|
645
|
+
being note: `deriveBeingInfo` and the copy it replaces produce byte-identical
|
|
646
|
+
output for all 95, deriving a skills map for 95 and gear for 2.
|
|
647
|
+
|
|
648
|
+
### Patch Changes
|
|
649
|
+
|
|
650
|
+
- a89a065: **Release from merged changesets instead of a remembered command**
|
|
651
|
+
|
|
652
|
+
Fixes [#15](https://github.com/HeroicLands/content-build/issues/15). Releasing was
|
|
653
|
+
hand-driven — bump `package.json` on a branch, merge, then remember
|
|
654
|
+
`gh release create`, because cutting the Release is what published. Nothing
|
|
655
|
+
enforced the last step, so on 2026-08-21 `main` carried 0.5.1 while npm served
|
|
656
|
+
0.4.0: two versions merged and never published, with no check red.
|
|
657
|
+
|
|
658
|
+
- Every pull request now declares its bump as a `.changeset/*.md` file, and CI's
|
|
659
|
+
**Changeset declared** job fails one that does not. `npx changeset add --empty`
|
|
660
|
+
is how a change says it needs no release — explicitly, rather than by omission.
|
|
661
|
+
- Merging to `main` opens a **Version Packages** pull request carrying the bump
|
|
662
|
+
and the rewritten `CHANGELOG.md`. An unreleased state is now a pull request
|
|
663
|
+
waiting in the queue rather than nothing at all.
|
|
664
|
+
- Merging that runs `changeset publish`: npm publish, the `v<version>` tag, and
|
|
665
|
+
the GitHub Release with the changelog section as its body. The OIDC Trusted
|
|
666
|
+
Publishing step is unchanged and still last; there is still no `NPM_TOKEN`, and
|
|
667
|
+
re-running on a published version is a no-op.
|
|
668
|
+
- `CHANGELOG.md` is seeded from the eleven hand-cut Releases so far and now ships
|
|
669
|
+
with the package.
|
|
670
|
+
|
|
671
|
+
<!-- Sections at 0.16.0 and above are generated by `changeset version` from
|
|
672
|
+
the changesets merged into `main`. Sections at 0.15.0 and below predate
|
|
673
|
+
that pipeline and are the hand-written GitHub Release notes, kept verbatim
|
|
674
|
+
(headings demoted one level to sit under their version) so no history was
|
|
675
|
+
lost in adopting it. -->
|
|
676
|
+
|
|
677
|
+
## 0.15.0
|
|
678
|
+
|
|
679
|
+
_2026-08-22 — one config file, two build packages_
|
|
680
|
+
|
|
681
|
+
**One repository describes itself in one file — and now that file serves both build packages.**
|
|
682
|
+
|
|
683
|
+
#### A reserved `packageBuild:` section
|
|
684
|
+
|
|
685
|
+
`defineConfig` accepts a `packageBuild:` mapping, validates that it _is_ a mapping, and hands it back frozen and **uninterpreted**. `@heroiclands/package-build` validates everything inside it.
|
|
686
|
+
|
|
687
|
+
```yaml
|
|
688
|
+
packageKind: systems # read from the top level, never restated below
|
|
689
|
+
foundryPackage: sohl
|
|
690
|
+
|
|
691
|
+
packageBuild:
|
|
692
|
+
assets:
|
|
693
|
+
- { from: lang, to: lang }
|
|
694
|
+
deploy:
|
|
695
|
+
envPrefix: SOHL
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
The two packages split by **input** — content-build reads the content tree, package-build reads `lang/`, `styles/`, `src/`, the assets and the manifest template — so neither should learn the other's schema. A section rather than a scatter of top-level keys, because that keeps the unknown-key guard intact for everything around it: that guard is what catches a typo'd `packs` before it becomes an empty compendium.
|
|
699
|
+
|
|
700
|
+
The alternative was a second config file, and it would have restated `packageKind` and `foundryPackage` — two places for one fact. That is exactly what every consumer's `push-stage.mjs` did, hard-coding `packageKind: "systems"` and `packageId: "sohl"` beside a configuration that already declared both.
|
|
701
|
+
|
|
702
|
+
#### `assets` is retired
|
|
703
|
+
|
|
704
|
+
The key had been part of the contract since the pack-config hoist — validated and frozen on every load, and read by **nothing**. The job it describes is `stageAssets`, which belongs to package-build, and each consumer did it from a local table instead. It now lives at `packageBuild.assets`.
|
|
705
|
+
|
|
706
|
+
No configuration anywhere declared it — not SoHL's, not this package's own — so the removal costs nobody a migration.
|
|
707
|
+
|
|
708
|
+
#### Upgrading
|
|
709
|
+
|
|
710
|
+
`^0.x` never crosses a minor, so no consumer moves until it bumps deliberately. For most, this release changes nothing: adopt it when you adopt `@heroiclands/package-build`'s command line.
|
|
711
|
+
|
|
712
|
+
**Full changelog:** https://github.com/HeroicLands/content-build/compare/v0.14.0...v0.15.0
|
|
713
|
+
|
|
714
|
+
## 0.14.0
|
|
715
|
+
|
|
716
|
+
_2026-08-22_
|
|
717
|
+
|
|
718
|
+
Released with no notes — see [v0.14.0](https://github.com/HeroicLands/content-build/releases/tag/v0.14.0).
|
|
719
|
+
|
|
720
|
+
## 0.13.0
|
|
721
|
+
|
|
722
|
+
_2026-08-22 — links and reachability as commands_
|
|
723
|
+
|
|
724
|
+
Two things a consumer should not have to write a script for.
|
|
725
|
+
|
|
726
|
+
### `content-build links [root]`
|
|
727
|
+
|
|
728
|
+
Checks that every link in a content tree lands, reporting:
|
|
729
|
+
|
|
730
|
+
- a **dead `#anchor`** — a page id is derived by hashing the note id and the anchor slug, and nothing else checks that a heading declaring it exists;
|
|
731
|
+
- a **dead qualified address** — a `type-shortcode` target resolving to no note is a typo (a bare `[[Name]]` that finds nothing is a worldbuilding placeholder, and is left alone);
|
|
732
|
+
- a **wikilink authored in frontmatter** — both builds copy frontmatter through verbatim, so it publishes as literal `[[…]]` text;
|
|
733
|
+
- a **vendored manifest that has drifted out of reach** — readable is not the same as addressable, and a key shape the lookup cannot parse makes every cross-package link miss while each page still reads correctly.
|
|
734
|
+
|
|
735
|
+
All of it is package-agnostic, so a consumer needs no script of its own.
|
|
736
|
+
|
|
737
|
+
### `content-build reachability <dir> [file] [--index <shortcode>]`
|
|
738
|
+
|
|
739
|
+
A documentation set is a **book, not a pile of notes**: it has a page one, and everything in it should follow from that page by reading. A note with no inbound link still compiles and still publishes — it is simply impossible to arrive at, and nothing else notices, because every other check asks whether a link _lands_, never whether a document is _reached_.
|
|
740
|
+
|
|
741
|
+
The corpus is named on the command line because it never changes for a repository:
|
|
742
|
+
|
|
743
|
+
```json
|
|
744
|
+
"lint:reachability:rules": "content-build reachability Rules --index glossary",
|
|
745
|
+
"lint:reachability:guide": "content-build reachability User_Guide --index glossary"
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
`--index` marks a page walked **to** but not **through** — an index links to nearly everything it covers, so traversing one makes the check vacuous. A corpus whose entry page is missing exits 1 rather than reporting every page as an orphan.
|
|
749
|
+
|
|
750
|
+
`walkReachability` is exported too, for a caller that wants the graph rather than a report.
|
|
751
|
+
|
|
752
|
+
### Also
|
|
753
|
+
|
|
754
|
+
- `engine/foreign-manifests.mjs` — the addressability guard, beside the key format it guards rather than in whichever consumer loads a manifest.
|
|
755
|
+
- `positionOfLiteral` in `engine/diagnostics.mjs` — for a finding about a literal in a file that is neither a note body nor frontmatter.
|
|
756
|
+
|
|
757
|
+
### Verified
|
|
758
|
+
|
|
759
|
+
Against the SoHL content tree, matching what its own scripts report: 1457 notes, every anchor landing, every qualified address resolving, 21 cross-package references via manifest, and 73/73 rules plus 43/43 user guide documents reachable. 854 tests.
|
|
760
|
+
|
|
761
|
+
Purely additive — but a `^0.12` pin will not cross to 0.13.0, so each consumer bumps its pin **and lockfile** deliberately.
|
|
762
|
+
|
|
763
|
+
## 0.12.0
|
|
764
|
+
|
|
765
|
+
_2026-08-22 — link resolution and the link audit_
|
|
766
|
+
|
|
767
|
+
Three link defects survive both content builds silently, so neither the pack compilers nor a site build catches them:
|
|
768
|
+
|
|
769
|
+
- **a dead `#anchor`** — a page id is derived by hashing the note id and the anchor slug, and nothing checks that a heading declaring it exists;
|
|
770
|
+
- **a dead qualified address** — a `type-shortcode` target resolving to no note is a typo (a bare `[[Name]]` that finds nothing is not: that is a worldbuilding placeholder, and is left alone);
|
|
771
|
+
- **a wikilink authored in frontmatter** — both builds copy frontmatter through verbatim, so it publishes as literal `[[…]]` text.
|
|
772
|
+
|
|
773
|
+
The checks for all three lived in the SoHL repository, inspecting only its own tree. `engine/content-links.mjs` builds the resolution index both builds construct — the type-scoped alias map, the `type/shortcode` and `doc<type>/shortcode` addresses, the vendored foreign manifests — and reports what lands nowhere.
|
|
774
|
+
|
|
775
|
+
**It parses links the way the builds do now.** It carried its own copy of the wikilink pattern: the _third_ in this codebase, and the same drifted one that let an unclosed bracket swallow a document. The checker was parsing more loosely than the compilers it was checking.
|
|
776
|
+
|
|
777
|
+
**Corpus reachability and retired hostnames are deliberately absent.** Both are statements about what one package publishes rather than about the note format, and both are served by the link graph the module returns (`notes`, `linksOf`, `resolve`) — so a consumer keeps those checks without keeping its own resolver.
|
|
778
|
+
|
|
779
|
+
Verified against the SoHL content tree: 1457 notes, 0 dead anchors, 0 dead addresses, 0 frontmatter wikilinks, and the same 21 cross-package references answered by manifest that its own script reports. 848 tests.
|
|
780
|
+
|
|
781
|
+
Purely additive — but a `^0.11` pin will not cross to 0.12.0, so each consumer bumps its pin **and lockfile** deliberately.
|
|
782
|
+
|
|
783
|
+
## 0.11.0
|
|
784
|
+
|
|
785
|
+
_2026-08-22 — arms-and-armour abbreviations_
|
|
786
|
+
|
|
787
|
+
Four words this content names constantly gain abbreviations:
|
|
788
|
+
|
|
789
|
+
| word | short |
|
|
790
|
+
| -------- | ------ |
|
|
791
|
+
| `sword` | `swd` |
|
|
792
|
+
| `shield` | `shld` |
|
|
793
|
+
| `round` | `rnd` |
|
|
794
|
+
| `battle` | `btl` |
|
|
795
|
+
|
|
796
|
+
The table had none of them, so `Round Shield` addressed a page at `round-shield` where the convention is `rnd-shld` — and the vowel reduction a shortcode falls back to produced `roundshild`, a shortening nobody would have chosen by hand.
|
|
797
|
+
|
|
798
|
+
Whole-word matching handles the near-misses with no special casing: `Broadsword` is one word, so `sword`'s rule does not reach inside it and the name stays whole.
|
|
799
|
+
|
|
800
|
+
**Derived addresses change, which is why this is a minor.** A page whose name contains one of these words now publishes at a different URL, and a shortcode suggested from such a name differs too. A `^0.10` pin will not cross to 0.11.0 — each consumer bumps deliberately, and regenerates any copy of the table it keeps.
|
|
801
|
+
|
|
802
|
+
Verified against the SoHL content tree: 1457 notes still yield 1457 distinct URLs, and the compiled packs remain byte-identical across all 2,828 documents. 829 tests.
|
|
803
|
+
|
|
804
|
+
## 0.10.0
|
|
805
|
+
|
|
806
|
+
_2026-08-22 — one wikilink syntax, one slug rule_
|
|
807
|
+
|
|
808
|
+
An authored `[[…]]` compiles to two addresses — a Foundry `@UUID` for the packs, a URL for the web — and those destinations are the only thing that legitimately differs. The syntax was written twice and **had already drifted**: the web side's pattern omitted `\n`, so an unclosed bracket swallowed everything up to the next `]]` anywhere in the document. `engine/wikilink-syntax.mjs` now owns the pattern and the parse, and both resolvers consume it.
|
|
809
|
+
|
|
810
|
+
**Breaking renames.** The names now say which address space each resolves into, since that is the whole of the difference:
|
|
811
|
+
|
|
812
|
+
- `engine/kb-wikilinks.mjs` → `engine/web-wikilinks.mjs`
|
|
813
|
+
- `resolveKbWikilinks` → `resolveWebWikilinks`
|
|
814
|
+
- barrel namespace `kbWikilinks` → `webWikilinks`
|
|
815
|
+
|
|
816
|
+
"kb" named one consumer's site section; the resolver already served any site. "html" would be wrong too — it emits Markdown.
|
|
817
|
+
|
|
818
|
+
**One slug rule.** Four slug-shaped transforms had drifted, and three dropped non-ASCII letters instead of transliterating them: `Kûrbúl Helm` published at `kurbul-helm` while its pack file was `k-rb-l-helm` and a link to a heading of that name pointed at `#k-rb-l-helm`. Twenty-two notes in the SoHL tree were affected. `engine/content-slug.mjs` owns the rule; `helpers`, `web-wikilinks` and `compendiums` consume it. `compendiums` was the worst — `.replace("'", "")` with a _string_ argument stripped only the first straight apostrophe and never a curly one.
|
|
819
|
+
|
|
820
|
+
**`engine/abbreviations.mjs`** — the conventional shortenings for this setting's vocabulary (ranks, offices, materials, units), matched greedily longest-first, whole words only. Applied to **document addresses only**: an anchor key is written by hand, and abbreviating a heading broke a real map pin (`locations.stair-foot` against a heading that became `stair-ft`).
|
|
821
|
+
|
|
822
|
+
**`protectCode`** joins `codeRegions` and `replaceOutsideCode` in `engine/code-fences.mjs`.
|
|
823
|
+
|
|
824
|
+
Verified against the SoHL content tree: 1,457 notes yield 1,457 distinct URLs, and the compiled packs are byte-identical across all 2,828 documents. 827 tests.
|
|
825
|
+
|
|
826
|
+
## 0.9.0
|
|
827
|
+
|
|
828
|
+
_2026-08-22 — lint a content tree's addresses_
|
|
829
|
+
|
|
830
|
+
The three rules a content note's **identity** is authored against move into this package, where every consumer gets them, instead of living in the SoHL repository where they only ever inspected SoHL's own tree (#20).
|
|
831
|
+
|
|
832
|
+
- **Shape** — a `shortcode` is strictly ASCII-alphanumeric. It is the identity key referenced from saved world data, and half of the `type-shortcode` address, whose parse needs the separating hyphen to be the only hyphen.
|
|
833
|
+
- **Uniqueness** — `(type, shortcode)` names one note.
|
|
834
|
+
- **Alias** — the note physically carries its own address in `aliases`, exactly once. Obsidian resolves a wikilink against the files on disk, so without the alias the address form resolves in the build and is dead in the editor.
|
|
835
|
+
|
|
836
|
+
```bash
|
|
837
|
+
npx content-build lint # the configured `paths.content`
|
|
838
|
+
npx content-build lint some/tree # or a tree named outright
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
It compiles nothing, opens no LevelDB and needs no Foundry manifest, so it takes about a second and can gate a commit. An empty or untyped tree **fails** rather than passing: "every one of nothing is unique" is a vacuous pass, and it is exactly what a tree that failed to check out produces.
|
|
842
|
+
|
|
843
|
+
**Why this mattered.** Pointed at the three real trees, two of which nothing had ever checked: `sohl` 1457 notes / 0 findings (matching its own guards exactly), `thalorna` 1738 notes / 4 findings, `kethira` 363 notes / **363 findings** — not one note there carries its address, so the address form of a wikilink has never resolved in that vault.
|
|
844
|
+
|
|
845
|
+
**Also fixes SoHL#1678.** The uniqueness rule now states what the pipeline actually enforces — a document is addressed by `(type, shortcode)` across _every_ pack of its document type — rather than the per-pack scope that #1566 made false once a note could declare `pack:`. Duplicates are reported once per offending note, each naming the others.
|
|
846
|
+
|
|
847
|
+
**New API:** `engine/content-lint` (`lintContentTree`, `auditNoteAliases`, `isAddressAlias`, `isValidShortcode`, `SHORTCODE_PATTERN`), the `contentLint` barrel export, and `positionInFrontmatter` in `engine/diagnostics`.
|
|
848
|
+
|
|
849
|
+
Purely additive — but a `^0.8` pin will not cross to 0.9.0, so each consumer bumps its pin **and lockfile** deliberately.
|
|
850
|
+
|
|
851
|
+
## 0.8.0
|
|
852
|
+
|
|
853
|
+
_2026-08-21 — declarative item fields_
|
|
854
|
+
|
|
855
|
+
**Item builders now declare the frontmatter they consume.**
|
|
856
|
+
|
|
857
|
+
The mapping from a note's `sohl:` frontmatter to the emitted `system` block
|
|
858
|
+
lived inside each builder's function body, so nothing could read it — not a
|
|
859
|
+
documentation generator, not a validator, not a person (#22).
|
|
860
|
+
|
|
861
|
+
The declaration is now the only statement of that mapping, and the builder is
|
|
862
|
+
generated from it:
|
|
863
|
+
|
|
864
|
+
- `engine/field-spec.mjs` — the declaration primitives, the coercions, and
|
|
865
|
+
`buildFromFields`, which turns a field list into the builder that runs.
|
|
866
|
+
- `sohl/item-fields.mjs` — all thirteen SoHL item types, each field with its
|
|
867
|
+
name, target, shape, requiredness, default and a one-line description.
|
|
868
|
+
- `engine/field-reference.mjs` and `content-build docs item-fields` — the
|
|
869
|
+
authoring reference, rendered from whatever the resolved configuration
|
|
870
|
+
declares.
|
|
871
|
+
|
|
872
|
+
**New in the configuration contract:** an `itemBuilders` entry may carry
|
|
873
|
+
`fields` alongside `system` and `img`, so a consuming repository declares — and
|
|
874
|
+
documents — its own item types the same way. The key is optional; a type that
|
|
875
|
+
omits it compiles exactly as before and is simply undocumented.
|
|
876
|
+
|
|
877
|
+
**No behaviour change.** Compiling the SoHL content tree before and after
|
|
878
|
+
produces 2,828 byte-for-byte identical pack documents, 1,230 of them items.
|
|
879
|
+
|
|
880
|
+
Consumers pick this up with a pin bump; nothing breaks on the old one.
|
|
881
|
+
|
|
882
|
+
## 0.7.0
|
|
883
|
+
|
|
884
|
+
_2026-08-21 — parseable, located diagnostics_
|
|
885
|
+
|
|
886
|
+
**Diagnostics about a content note now name the file, line and column.**
|
|
887
|
+
|
|
888
|
+
A warning used to name the note by `name.full`, which is not an address — four
|
|
889
|
+
identical warnings on one note were indistinguishable, and each had to be hunted
|
|
890
|
+
for in a file the build had already read. Every diagnostic is now emitted in the
|
|
891
|
+
form every C-family compiler, `tsc` and ESLint already use, so an editor's error
|
|
892
|
+
matcher or a CI annotator resolves it with no knowledge of this build:
|
|
893
|
+
|
|
894
|
+
```text
|
|
895
|
+
assets/content/Regions/Capital_Nome.md:43:635: warning: unresolved wikilink [[Kenbet_Pat|Kenbet'Pat]] (unknown) in "The Capital Nome"
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
Two rules keep it parseable: the locator starts the line (diagnostics bypass
|
|
899
|
+
`loglevel`, whose `[timestamp] [WARN]:` prefix sits where a parser reads the
|
|
900
|
+
path from), and a field is dropped rather than guessed — nothing defaults to
|
|
901
|
+
`1:1`.
|
|
902
|
+
|
|
903
|
+
#### Breaking
|
|
904
|
+
|
|
905
|
+
- **`expandNoteTables` returns `{ markdown, lineMap }`**, not the markdown
|
|
906
|
+
string. `engine/*` is a public export, so a direct importer must be updated.
|
|
907
|
+
A `^0.6` pin will not cross to 0.7.0; each consumer bumps deliberately.
|
|
908
|
+
|
|
909
|
+
#### Also in this release
|
|
910
|
+
|
|
911
|
+
- `parseMarkdownFile` additionally returns `bodyLine` / `bodyColumn`.
|
|
912
|
+
- `expandContentTables` additionally returns `lineMap`; its `errors` entries
|
|
913
|
+
carry the failing directive's `line`.
|
|
914
|
+
- `convertWikilinks`' `unresolved` entries carry `offset`.
|
|
915
|
+
- `convertNoteWikilinks` accepts `file` / `bodyLine` / `bodyColumn` / `lineMap`;
|
|
916
|
+
its thrown errors carry `file` and `position`.
|
|
917
|
+
- `BasePackCompiler` publishes the note being compiled as `currentNote` and
|
|
918
|
+
exposes `noteWarn` / `noteError`, so a pass reports a position without every
|
|
919
|
+
method being handed one. The map warnings and the actor-compiler errors go
|
|
920
|
+
through it too — both previously named a note and no file.
|
|
921
|
+
- A link a `dataview` table generated is blamed on the directive that produced
|
|
922
|
+
it and reports **no** column, since there is no authored character to point at.
|
|
923
|
+
|
|
924
|
+
Progress and summary lines are unchanged.
|
|
925
|
+
|
|
926
|
+
Closes #17.
|
|
927
|
+
|
|
928
|
+
## 0.6.0
|
|
929
|
+
|
|
930
|
+
_2026-08-21 — default art by builder, ambiguous links now fail_
|
|
931
|
+
|
|
932
|
+
**Breaking**
|
|
933
|
+
|
|
934
|
+
- An item type's **default art now travels with its builder** (#11). An
|
|
935
|
+
`itemBuilders` entry may be `type: buildFn` as before, or
|
|
936
|
+
`type: { system: buildFn, img: "…" }`. A consuming repository can finally
|
|
937
|
+
declare art for its own item types; previously art was looked up in a table
|
|
938
|
+
this package ships and a consumer could not add to, so a consumer's own type
|
|
939
|
+
compiled only while every one of its notes carried an explicit `img:`.
|
|
940
|
+
- An **ambiguous wikilink now fails the compile** instead of warning (#13), and
|
|
941
|
+
the message names the notes that collided rather than the note that cites
|
|
942
|
+
them. An ambiguous address matched real content twice; there is no defensible
|
|
943
|
+
way to pick one, and the fix is mechanical — write the qualified form. The
|
|
944
|
+
knowledgebase build has always treated this as fatal, so the two builds now
|
|
945
|
+
agree. Verified against every consumer: 0 links would fail in
|
|
946
|
+
Song-of-Heroic-Lands-FoundryVTT, sohl-thalorna or sohl-kethira-basic.
|
|
947
|
+
|
|
948
|
+
**Fixed**
|
|
949
|
+
|
|
950
|
+
- A Scene `levels` entry is described by the shape it actually has (#12).
|
|
951
|
+
- An empty `relation` / `skillAptitudes` list reads as an empty map (#10).
|
|
952
|
+
|
|
953
|
+
**Upgrading**
|
|
954
|
+
|
|
955
|
+
Consumers pin `^0.4.0` = `>=0.4.0 <0.5.0`, so this release does not reach anyone
|
|
956
|
+
on its own — both the manifest and the **lockfile** must move, since `npm ci`
|
|
957
|
+
installs what is locked. Dependabot is configured in all three consumers with
|
|
958
|
+
this package as its own single-package group and will open one pull request each
|
|
959
|
+
now that a version exists to bump to.
|
|
960
|
+
|
|
961
|
+
## 0.4.0
|
|
962
|
+
|
|
963
|
+
_2026-08-20 — retire the character/creature content types_
|
|
964
|
+
|
|
965
|
+
Retires `character` and `creature` in favour of a single `being`, reported rather than silently routed to the items pack. Also carries the lazy-config change from #4. See #5, #6.
|