@heroiclands/package-build 4.0.0 → 6.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.md CHANGED
@@ -1,5 +1,406 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 5538fdf: Check `packFolders` against the derived `packs[]` (#81).
8
+
9
+ `packageBuild.manifest.packFolders` is the one **declared** manifest key that
10
+ names something the build **derives**. Every other declared key states a fact
11
+ about the package (`title`, `socket`, `grid`) or addresses a staged file
12
+ (`esmodules`, `styles`, `languages`) — a staged file being a different relation,
13
+ answered against the stage rather than against configuration. Surveyed across
14
+ all six HeroicLands packages, no other declared key names a derived value, so
15
+ this is the only place the two halves could drift.
16
+
17
+ They did. `HarnMaster-3-FoundryVTT` shipped a folder naming `character`,
18
+ `possessions`, `esoteric` and `system-help`; three had not existed since its
19
+ compendium was consolidated into one `items` pack, and `items` — 1,577 of 1,597
20
+ documents — was named by no folder at all. Foundry rendered the folder holding
21
+ one journal pack with the entire item compendium loose beside it, and the build
22
+ reported nothing at any point (HarnMaster-3-FoundryVTT#420).
23
+
24
+ **Two findings, deliberately different severities**
25
+
26
+ | Finding | Severity | Why |
27
+ | ----------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
28
+ | a folder names a pack the package does not ship | **error** | Foundry silently skips a name it cannot resolve, so the declaration does nothing; no arrangement intends it |
29
+ | a pack no folder names | **warning** | legal, and a root-level pack can be deliberate — but a package that declared a folder rarely meant to leave one out |
30
+ | no `packFolders` declared at all | nothing | everything at the root is an arrangement, not an omission |
31
+
32
+ Giving the two one severity gets one of them wrong: erroring on an ungrouped
33
+ pack fails working packages over a matter of taste, and warning on an
34
+ unresolvable name reproduces the defect this exists to catch.
35
+
36
+ The comparison descends through nested folders — Foundry's
37
+ `PackageCompendiumFolder` re-declares itself while `depth < 4` — so a nested
38
+ name is checked in both directions rather than being missed and then reported as
39
+ ungrouped.
40
+
41
+ **Reported where the reader can open it**
42
+
43
+ Findings carry the config key path of the offending scalar, which
44
+ `positionOfYamlPath` (new, in `engine/diagnostics.mjs`) resolves against the
45
+ configuration file. A position that cannot be established honestly — an `.mjs`
46
+ configuration, an unreadable file — is dropped rather than guessed, so the line
47
+ degrades from `file:line:column:` to `file:` to no locator:
48
+
49
+ ```text
50
+ package-build.config.yaml:164:23: error: packFolders: folder "HârnMaster 3 System" names pack "character", which this package does not ship (packs: items, system-help)
51
+ package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
52
+ ```
53
+
54
+ An error **stops the write**: a manifest already known to describe packs that do
55
+ not exist should not reach the stage, where the next command would deploy it.
56
+
57
+ **Why major**
58
+
59
+ It newly fails a build that passes today. Measured against every real consumer
60
+ as each stands: `sohl`, `sohl-kethira-basic`, `harn-ensemble` and
61
+ `harn-adventures` are clean; `sohl-thalorna` warns once, for an `actors` pack no
62
+ folder names, and still builds; and `HarnMaster-3-FoundryVTT`'s `main` fails
63
+ with the three errors above, which is HarnMaster-3-FoundryVTT#420 — filed, and
64
+ fixed in its open PR #426. That is a real defect reported rather than
65
+ accommodated, but it is still a red build a consumer would meet on a blind
66
+ upgrade, so it takes the major.
67
+
68
+ Also new and exported: `packFolderFindings` (the rule, pure) from
69
+ `@heroiclands/package-build/manifest`, `positionOfYamlPath` from
70
+ `@heroiclands/package-build/engine/diagnostics`, and `packConfigPath` from
71
+ `@heroiclands/package-build/engine/pack-config` — the file `loadPackConfig`
72
+ actually read, so a finding about a configured value names it rather than
73
+ re-deriving a path free to disagree. `writeManifest` takes an optional
74
+ `configFile`; omitting it costs the position, not the finding.
75
+
76
+ ### Minor Changes
77
+
78
+ - 46e0c10: Check the package homepage's own links (#54).
79
+
80
+ The homepage is the page a reader arrives at, and it was the one page nothing
81
+ checked. Every other note addresses the corpus with wikilinks, which
82
+ `content-build links` resolves; a landing addresses the web the way the web does
83
+ — markdown links and `landing:` `url` / `href` fields — and none of those went
84
+ through a checker at all. SoHL's landing pointed at `kb/creature/` and
85
+ `kb/character/` from the day those two types merged into `being`: two 404s on
86
+ the package's front page, surviving every build, found only by a person reading
87
+ the page.
88
+
89
+ **Both halves of the note are in scope, and the real pages are why.** Of the six
90
+ homepages authored today, four carry every link in the body as ordinary markdown
91
+ and two carry them in `landing:` front matter — and the one whose dead links
92
+ prompted this has an _empty body_. A body-only check would have found nothing on
93
+ the page it was written for. So `landing.install.url`, every card and card-link
94
+ `url` / `href`, the markdown links inside the prose fields (`lead`, `closing`,
95
+ `install.intro`, `install.note`, a card's `description`, a link's `note`) and the
96
+ body's own markdown links are all read.
97
+
98
+ **`url` and `href` are not the same address.** The theme resolves a `url`
99
+ against the site with `relURL`, so a package writes `kb/rules/` and is served
100
+ `/sohl/kb/rules/` without naming its own prefix; an `href` is an address that is
101
+ _already_ resolved and is used verbatim, which is what `cards.source: sections`
102
+ fills in. A leading `/` is therefore a defect in a `url` — Hugo prefixes it a
103
+ second time — and correct in an `href`, so the two are not checked the same way.
104
+
105
+ **What is reported**, each finding naming the form to write instead:
106
+
107
+ | Finding | Why |
108
+ | ---------------------------- | -------------------------------------------------------------------------- |
109
+ | A **retired content type** | `kb/creature/` after `creature` became `being` — the engine knows. |
110
+ | A **hardcoded absolute URL** | Into this package's own prefix, or into one a vendored manifest names. |
111
+ | A **root-relative `url:`** | `relURL` prefixes it again. `href:` is exempt — verbatim is what it means. |
112
+ | A **wikilink** | Nothing resolves one here: a homepage is published verbatim in every mode. |
113
+
114
+ That last one settles a question rather than deferring it. A homepage does
115
+ **not** get the wikilink resolution every other note body gets, because in
116
+ `homepage` mode the content tree is never walked — there is no index for a
117
+ wikilink to resolve against, and giving the page one would make the mode depend
118
+ on exactly the machinery its licensing fence exists to not build. A wikilink on
119
+ a landing is therefore reported, not resolved.
120
+
121
+ **What is deliberately not attempted.** Whether an external URL answers: there is
122
+ no network at build time and a build must not go red because a third party is
123
+ down. And whether a live in-site address names a page that exists: several
124
+ surfaces a landing routes to are produced by other tools entirely — generated API
125
+ documentation, hand-authored Hugo sections — so this build does not hold the set
126
+ of published pages and would report a working link as dead. A bare
127
+ `https://www.heroiclands.org/<package>/` is left alone for the same reason it
128
+ cannot be improved: a package homepage is in no link manifest, so there is no
129
+ better form to write.
130
+
131
+ **Minor rather than major, measured rather than assumed.** A new lint error that
132
+ fails a previously-passing consumer would be breaking. All six HeroicLands
133
+ content packages were run against it — `sohl`, `hm3`, `thalorna`, `kethira`,
134
+ `harnensemble`, `harnadventures`, including the two homepages that exist only in
135
+ open pull requests — and every one is clean; the four whose trees are checked out
136
+ in full pass `links` end to end. Run against SoHL's landing as it stood _before_
137
+ the port, the check reports both dead links, at their line and column.
138
+
139
+ It rides in the existing pass rather than beside it: no new command, no second
140
+ walk, and a consumer that already runs `content-build links` gets it with no
141
+ change.
142
+ - d8ce7b3: Derive the compile order from what each pass reads, instead of trusting the
143
+ order `packs:` happens to declare (#73).
144
+
145
+ `generatePacksJson` ran its passes in declaration order, but the actors pass
146
+ resolves each being's embedded items against the **output** of the item passes —
147
+ the JSON under `build/packs-json/`, not the content tree. A package declaring its
148
+ Actor pack first therefore compiled only where an earlier run had already left
149
+ that directory populated: green on every local tree that had built once, exit 1
150
+ on a cold one, over a message naming a missing directory rather than the ordering
151
+ that caused it.
152
+
153
+ `build/` is gitignored, so **every fresh checkout and every CI runner is cold**.
154
+ `sohl-kethira-basic` shipped exactly that list and its release path was broken;
155
+ the failure had not fired only because an unrelated lint failure exited first.
156
+
157
+ **What changed**
158
+
159
+ - A compiler declares the document types whose compiled output it reads —
160
+ `static readsPackOutputOf` on `BasePackCompiler`, `["Item"]` on `Actors`. A
161
+ consumer registering a compiler of its own declares its dependencies the same
162
+ way.
163
+ - `orderPassesByDependency` (exported from `engine/generate.mjs`) schedules each
164
+ pass after **every** pack of every type it names — a being addresses an item by
165
+ `(type, shortcode)` without knowing which Item pack ships it, so waiting for
166
+ one of several would resolve some beings and silently fail others. The
167
+ reordering is the smallest one that works: the earliest declared pass whose
168
+ dependencies have all run goes next, so a list already in a workable order is
169
+ compiled exactly as declared. The build logs the derived order only when it
170
+ differs from the declared one.
171
+ - **The declared list is untouched**, which is the point: it is also the
172
+ manifest's `packs` array, and a consumer orders that for a reader browsing
173
+ compendiums. The two are now allowed to disagree, so fixing a cold build no
174
+ longer means reordering the shipped manifest away from its `packFolders`.
175
+ - The case ordering cannot answer — `content-build package compile <name>`, which
176
+ runs one pass and no other — is now reported in this project's diagnostic form,
177
+ naming the pack that waits, the pack it waits on and the fix, instead of
178
+ throwing about a directory:
179
+
180
+ ```text
181
+ error: pack "characters" (Actor) reads the compiled output of the Item pack
182
+ "characteristics", which this run does not compile and which
183
+ build/packs-json/characteristics does not hold — compile the whole
184
+ package, or compile "characteristics" first
185
+ ```
186
+
187
+ **Bump**
188
+
189
+ _Minor, not patch, and not major._ Minor because it adds public surface: two
190
+ exports on `engine/generate.mjs` and a third documented static switch on
191
+ `BasePackCompiler`, which is the registration point for a consumer's own
192
+ compiler.
193
+
194
+ **No previously-passing consumer build starts failing.** The change is strictly
195
+ permissive — configurations that failed now succeed, and configurations that
196
+ succeeded compile the same documents. Of the four HeroicLands packages, three
197
+ (`sohl`, `sohl-thalorna`, `HarnMaster-3-FoundryVTT`) declare an order the
198
+ derivation returns unchanged; `sohl-kethira-basic` is the one that moves, and its
199
+ `build/packs-json` is **byte-identical** across the two orders — 385 documents,
200
+ `diff -r` exit 0. The new single-pack diagnostic replaces a throw on exactly the
201
+ runs that already failed.
202
+ - f716902: Stop emitting `isEquipped` on a compiled gear item (#68).
203
+
204
+ `GEAR_COMMON` emitted `isEquipped: false` on every gear item, and no SoHL
205
+ DataModel declares the field. `GearDataModel` declares `isCarried`,
206
+ `containerId` and `sharedWithCohortIds` as its possession state, and nothing
207
+ else — Foundry discards the extra key when the document is constructed, so
208
+ every gear item in every consuming pack shipped a value that was thrown away at
209
+ load, with nothing at compile or load time saying so.
210
+
211
+ `GEAR_COMMON` is spread into all six gear types (`armorgear`, `concoctiongear`,
212
+ `containergear`, `miscgear`, `projectilegear`, `weapongear`), so this reached
213
+ every gear item of every consuming package.
214
+
215
+ This is the second instance of #35's defect and has the same root cause:
216
+ nothing compares a builder's emitted `system` block against the DataModel that
217
+ receives it. The general check is #60.
218
+
219
+ **The field was retired, not renamed.**
220
+ Song-of-Heroic-Lands-FoundryVTT#662 made the worn/equipped concept armour-only:
221
+ it removed `system.isEquipped` from the shared gear data model and gave
222
+ `ArmorGearDataModel` its own `system.isWorn`. That shipped in SoHL 0.8.0, so no
223
+ released system has read the key since. `isWorn` belongs to armour alone and is
224
+ not a target this declaration can be retargeted at — whether an `armorgear`
225
+ note should be able to author one is a separate content question, left open on
226
+ #68.
227
+
228
+ **Nothing to sweep.** Unlike `assocMysteryCode`, this was never authorable: the
229
+ declaration carried a `to` and a `value` but no `name`, so `readField` never
230
+ consulted the frontmatter and no note in any package could set it. It was
231
+ already absent from `authoredFields`, so the author-facing field reference is
232
+ unchanged. A consumer has no line to delete.
233
+
234
+ **This changes emitted documents**, so a consumer wants a rebuild rather than a
235
+ silent upgrade — though nothing downstream can have depended on the value.
236
+ Verified by recompiling two consumer trees at `main` before and after, comparing
237
+ every emitted document key-ordered:
238
+
239
+ - `sohl` — removes exactly 1019 `"isEquipped": false` keys from 1012 of its 3126
240
+ compiled documents: 1010 items (465 `miscgear`, 331 `armorgear`, 114
241
+ `containergear`, 82 `weapongear`, 18 `projectilegear`) and 9 more embedded in
242
+ gear-carrying actors. No other difference, and no document added or removed.
243
+ - `sohl-thalorna` — removes 97 of 2018 across 2555 documents (96 items: 71
244
+ `concoctiongear`, 25 `weapongear`). No other difference.
245
+
246
+ **A consumer that embeds a foreign item catalogue keeps the key until its
247
+ upstream republishes.** The 1921 `sohl-thalorna` occurrences this does not
248
+ remove are not emitted by this build at all: they are inherited verbatim from
249
+ the pinned `sohl@0.8.2` release pack its actors resolve against, which was
250
+ compiled by an earlier package-build. They clear when `sohl` cuts a release
251
+ built with this version, not before — so a consumer grepping its own output
252
+ after upgrading should expect the catalogue's share to remain.
253
+ - 07944a2: Resolve the `sohlKb` TypeDoc symbol map against the repository root, and stop
254
+ swallowing every failure to read it (#75).
255
+
256
+ `site.passOptions.symbolMap` is authored repo-relative, but `readSymbolMap` read
257
+ it against the process cwd and wrapped the read in a bare `catch` that returned
258
+ `{}`. A missing file, a malformed one, a permissions error, a path typo and a
259
+ correctly configured build with no symbols were all indistinguishable — and the
260
+ build exited 0 either way, publishing every `{@link}` as a code span instead of
261
+ a link into the API documentation. Driving `content-build site` from outside the
262
+ tree through `PACKAGE_BUILD_CONFIG` — how #51 was verified — silently dropped
263
+ 224 API links across 25 pages of the `sohl` knowledgebase, and nothing at any
264
+ stage reported it.
265
+
266
+ **What changed**
267
+
268
+ | State | Before | Now |
269
+ | -------------------------------------- | -------------------- | -------------------------------------------- |
270
+ | `symbolMap` unset | `{}`, silent | `{}`, silent — unchanged |
271
+ | Configured, readable | works from repo root | works from **any** directory |
272
+ | Configured, missing / unreadable | `{}`, exit 0 | build fails, naming the path and `errno` |
273
+ | Configured, malformed JSON | `{}`, exit 0 | build fails, naming the path and the JSON |
274
+ | Configured, JSON that is not an object | `{}`, exit 0 | build fails, naming the path |
275
+ | Configured, read | nothing | `resolved N API symbols from <path>` at info |
276
+
277
+ The count is reported because a map that loaded and a map that loaded _empty_
278
+ are otherwise indistinguishable without reading the emitted HTML, and an empty
279
+ one degrades every tag exactly as a missing one used to.
280
+
281
+ **Bump**
282
+
283
+ _Minor, not patch._ No key, export, or flag changed shape, and a consumer whose
284
+ map is where its configuration says it is sees only the new info line. But a
285
+ build that previously exited 0 can now fail — deliberately — which reverses the
286
+ module's own documented licence to run the knowledgebase before `npm run docs`
287
+ and publish degraded tags. A consumer that orders its pipeline that way must
288
+ either generate the map first or leave `symbolMap` unset. No known consumer is
289
+ affected: `Song-of-Heroic-Lands-FoundryVTT` commits `kb/data/api-symbols.json`.
290
+
291
+ ### Patch Changes
292
+
293
+ - bb08713: Stop `content-build lint` failing a homepage-only content tree (#77).
294
+
295
+ A package in `publish.site: homepage` mode may hold exactly one note, and a
296
+ homepage carries no `shortcode` **by design** — it is addressed by the package
297
+ rather than by a slug, so `HOMEPAGE_FIELDS` is empty. The vacuous-tree guard
298
+ keyed off the address map, so that tree produced no keys and was reported as a
299
+ missing checkout:
300
+
301
+ ```text
302
+ assets/content: error: holds no keyed content, so every rule here is vacuous — check that the content tree is present and that this is its root
303
+ ```
304
+
305
+ The tree was present, it was the root, and it held the one note the package is
306
+ meant to have. `harn-adventures` and `sohl-kethira-basic` both ship in that
307
+ mode, so for them the failure was permanent — and an expected failure trains its
308
+ author to stop reading the output, which is the one thing this guard needs them
309
+ to do.
310
+
311
+ The guard now reports an **empty walk** rather than an empty key set: a tree
312
+ holding notes is a tree, whatever they are keyed on, and only a tree holding
313
+ none is the absent one. Nothing about its strength changes — an empty tree, a
314
+ tree of untyped scaffolding, and a path that is not the content root each still
315
+ fail with the same diagnostic, now worded "holds no content notes".
316
+
317
+ `patch`, not `minor`: no tree that lints today reports anything different. The
318
+ only behaviour that changes is a false failure becoming a pass. The success line
319
+ gains its missing noun — `(0 address(es) across 1 note(s))` — since a
320
+ homepage-only pass is the first time it prints a zero.
321
+
322
+ ## 5.0.0
323
+
324
+ ### Major Changes
325
+
326
+ - 4da0dbc: Every package publishes an authored homepage at `/<contentPackage>/`, and
327
+ `publish.site` becomes a mode rather than a boolean (#51, #55).
328
+
329
+ **A `type: homepage` note (#51)**
330
+
331
+ A new engine-level content type that compiles into a **page** rather than into a
332
+ compendium document. Its whole frontmatter envelope is `type` and an optional
333
+ `title`, defaulting to `packageBuild.manifest.title` so the package's name is not
334
+ written twice:
335
+
336
+ ```markdown
337
+ ---
338
+ type: homepage
339
+ title: HârnMaster Kethira Basic
340
+ ---
341
+
342
+ What the module is, which system it needs, how to install it.
343
+ ```
344
+
345
+ It compiles into no document, appears in no pack and in no link manifest, and is
346
+ addressed by the **package** — `/<contentPackage>/` — rather than by a slug
347
+ derived from its name, so `name.full`, `shortcode` and `id` decide nothing on it.
348
+ It is written at the root of `site.out`, one level above the content mount.
349
+
350
+ The page is _authored, not assembled_. Deriving it from the manifest, the release
351
+ address and `relationships` was the obvious shortcut and produces a page nobody
352
+ chose the contents of — it cannot express that Kethira requires buying the book
353
+ from Keléstia, or which of twenty sections a reader should start with.
354
+
355
+ It is declared in `engine/note-schemas.mjs` rather than in the `sohl` item
356
+ registry: the `engine/` ÷ `sohl/` line is note-format knowledge against
357
+ game-system knowledge, and a homepage carries no `system` block and mirrors no
358
+ item builder. Reachability is the symptom that makes it obvious —
359
+ `HarnMaster-3-FoundryVTT` declares no `itemBuilders` at all, so a type living in
360
+ the SoHL registry would be unavailable to HM3 and every HM3 module, which is most
361
+ of the packages that need a homepage and nothing else.
362
+
363
+ **`publish.site` is a mode (#55) — breaking**
364
+
365
+ | Was | Write |
366
+ | ------------- | ---------------------------------- |
367
+ | `site: true` | `site: content` |
368
+ | `site: false` | `site: homepage` |
369
+ | absent | absent — the default is `homepage` |
370
+
371
+ `homepage` publishes the authored homepage and **no other page**; `content`
372
+ publishes it plus every page the content tree compiles to. There is no value
373
+ meaning "no web presence", because every package publishes its homepage.
374
+
375
+ Both booleans are **refused rather than mapped** onto the nearest mode, naming
376
+ the mode to write. `false` read as _this package has no web presence_, which
377
+ describes no package now, and a value silently reinterpreted reads to its author
378
+ as though it still means what it said.
379
+
380
+ **Homepage-only is a first-class mode, not an accommodation.**
381
+ `sohl-kethira-basic` (Keléstia Productions' Fan Material Guidelines) and
382
+ `harn-adventures` (HârnFanon under Lythia's terms) each publish a homepage and
383
+ nothing beneath it — two packages under two different fan-content licences. The
384
+ boundary is _published content_: journal text, artwork, item descriptions,
385
+ compiled notes. Because the failure mode is silent — a `site:` block added later
386
+ ships licensed content with nobody noticing — the mode **fences the content
387
+ surfaces off**: in `homepage` mode the tree is never walked for pages, and
388
+ `sections`, `trees`, `landing` and `backfillSections` emit nothing even when they
389
+ are declared. Measured against the real `sohl-kethira-basic` tree — 363 notes,
390
+ and a `site:` block deliberately declaring sections and a landing — the build
391
+ emits exactly one file.
392
+
393
+ `publish.manifests.publish` is a separate decision and stays `false` for both, for
394
+ an unrelated reason: a link manifest is the dependency edge that would stop a
395
+ module being withdrawable, and a homepage is one row in a routing table.
396
+
397
+ **Nothing else moves.** `publish.address`, `publish.manifests` and the whole
398
+ `site:` block are unchanged, and every address `sohl` already publishes is
399
+ byte-identical across the upgrade. Verified against the real tree: 1,669 emitted
400
+ files before, 1,670 after, the one addition being `kb/content/_index.md`; the
401
+ link manifest's 2,989 entries and all 3,126 compiled pack documents are
402
+ byte-identical with and without the homepage note.
403
+
3
404
  ## 4.0.0
4
405
 
5
406
  ### Major Changes