@heroiclands/package-build 5.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 +319 -0
- package/CONTENT.md +136 -13
- package/MIGRATING.md +41 -0
- package/README.md +30 -0
- package/bin/content-build.mjs +20 -3
- package/bin/package-build.mjs +3 -1
- package/engine/base-compiler.mjs +28 -3
- package/engine/content-links.mjs +221 -2
- package/engine/content-lint.mjs +12 -3
- package/engine/diagnostics.mjs +46 -0
- package/engine/generate.mjs +156 -7
- package/engine/homepage.mjs +109 -0
- package/engine/pack-config.mjs +21 -0
- package/engine/site-build.mjs +10 -4
- package/manifest.mjs +195 -0
- package/package.json +1 -1
- package/sohl/actors.mjs +14 -1
- package/sohl/item-fields.mjs +0 -5
- package/sohl/kb-passes.mjs +81 -14
- package/types/engine/base-compiler.d.mts +22 -0
- package/types/engine/content-links.d.mts +53 -2
- package/types/engine/diagnostics.d.mts +28 -0
- package/types/engine/generate.d.mts +50 -2
- package/types/engine/homepage.d.mts +49 -42
- package/types/engine/pack-config.d.mts +13 -0
- package/types/engine/site-build.d.mts +10 -4
- package/types/manifest.d.mts +67 -1
- package/types/sohl/kb-passes.d.mts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,324 @@
|
|
|
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
|
+
|
|
3
322
|
## 5.0.0
|
|
4
323
|
|
|
5
324
|
### Major Changes
|
package/CONTENT.md
CHANGED
|
@@ -77,8 +77,10 @@ paths:
|
|
|
77
77
|
stage: build/stage/packs
|
|
78
78
|
unpack: build/tmp/packs
|
|
79
79
|
|
|
80
|
-
# The one pack list.
|
|
81
|
-
#
|
|
80
|
+
# The one pack list. `packDirectories` and the manifest's `packs` array are both
|
|
81
|
+
# derived from it, so order it for a reader browsing compendiums — the compile
|
|
82
|
+
# order is worked out separately, from what each pass reads (see "Declaration
|
|
83
|
+
# order is presentation" below).
|
|
82
84
|
packs:
|
|
83
85
|
- { name: items, type: Item, label: Items, folders: item-folders.yaml }
|
|
84
86
|
- { name: journals, type: JournalEntry, label: Journals }
|
|
@@ -357,6 +359,55 @@ hand-authored `system.template.json` lived, and the package-id guard and the
|
|
|
357
359
|
top-level `compatibility.minimum`, the id is derived from `package.json`
|
|
358
360
|
`name`, and `@heroiclands/package-build` writes the manifest from this file.
|
|
359
361
|
|
|
362
|
+
### Declaration order is presentation, not compile order
|
|
363
|
+
|
|
364
|
+
`packs:` is the manifest's `packs` array as well, so a consumer orders it for a
|
|
365
|
+
reader browsing compendiums. It is **not** the order the passes run in, and it
|
|
366
|
+
does not have to be: the compile order is derived from what each pass reads.
|
|
367
|
+
|
|
368
|
+
One pass reads another's output today. The actors pass resolves each being's
|
|
369
|
+
embedded items against the JSON the item passes wrote — a being names an item by
|
|
370
|
+
`(type, shortcode)` and never by the pack it ships in, so **every** Item pack has
|
|
371
|
+
to be compiled before the Actor pass, not merely the first. A compiler states
|
|
372
|
+
that on itself:
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
export class Actors extends BasePackCompiler {
|
|
376
|
+
static readsPackOutputOf = Object.freeze(["Item"]);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The generator schedules each pass after the packs of every type it names, and
|
|
381
|
+
does so with the **smallest** reordering that works — the earliest declared pass
|
|
382
|
+
whose dependencies have all run goes next. A list already in a workable order is
|
|
383
|
+
therefore compiled exactly as declared, and one that is not moves only the
|
|
384
|
+
passes that had to move. When the two orders differ the build says so:
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
[INFO]: Pass order: characteristics, mysteries, characters — a pass that reads
|
|
388
|
+
another's output compiles after it, whatever order `packs:` declares.
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
This used to be the author's problem, and a nasty one: an Actor pack declared
|
|
392
|
+
first compiled only where an earlier run had already left `build/packs-json`
|
|
393
|
+
populated. `build/` is gitignored, so it was green on every local tree that had
|
|
394
|
+
built once and exit 1 on every fresh checkout and CI runner, over a message that
|
|
395
|
+
named a missing directory rather than the ordering that caused it (#73). A
|
|
396
|
+
consumer registering a compiler of its own declares its dependencies the same
|
|
397
|
+
way; a type no pack of which is declared is simply not waited for.
|
|
398
|
+
|
|
399
|
+
**Compiling one pack by name is the case ordering cannot answer.**
|
|
400
|
+
`content-build package compile <name>` runs the pass you asked for and no other,
|
|
401
|
+
so a dependency that is neither in the run nor already on disk is reported
|
|
402
|
+
rather than ordered around:
|
|
403
|
+
|
|
404
|
+
```text
|
|
405
|
+
error: pack "characters" (Actor) reads the compiled output of the Item pack
|
|
406
|
+
"characteristics", which this run does not compile and which
|
|
407
|
+
build/packs-json/characteristics does not hold — compile the whole
|
|
408
|
+
package, or compile "characteristics" first
|
|
409
|
+
```
|
|
410
|
+
|
|
360
411
|
### An item type's default art
|
|
361
412
|
|
|
362
413
|
A note that carries no `img:` gets its type's **default art**, and a type
|
|
@@ -425,17 +476,17 @@ npx content-build site [--out <dir>]
|
|
|
425
476
|
npx content-build reachability <dir> [file] [--index <shortcode>]
|
|
426
477
|
```
|
|
427
478
|
|
|
428
|
-
| Command | What it does
|
|
429
|
-
| -------------- |
|
|
430
|
-
| `package` | Compile the content tree into LevelDB packs, unpack a shipped pack back to JSON, or clean one. See [Install](#install).
|
|
431
|
-
| `docs` | Render a generated reference from the configured registries. `item-fields` is the item-frontmatter page.
|
|
432
|
-
| `lint` | Check a content tree's addresses and its frontmatter. See [Linting a content tree](#linting-a-content-tree).
|
|
433
|
-
| `links` | Check that every link in the tree lands: dead anchors, dead qualified addresses, wikilinks in frontmatter, drifted manifests. |
|
|
434
|
-
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown).
|
|
435
|
-
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to.
|
|
436
|
-
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest).
|
|
437
|
-
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website).
|
|
438
|
-
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point.
|
|
479
|
+
| Command | What it does |
|
|
480
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
481
|
+
| `package` | Compile the content tree into LevelDB packs, unpack a shipped pack back to JSON, or clean one. See [Install](#install). |
|
|
482
|
+
| `docs` | Render a generated reference from the configured registries. `item-fields` is the item-frontmatter page. |
|
|
483
|
+
| `lint` | Check a content tree's addresses and its frontmatter. See [Linting a content tree](#linting-a-content-tree). |
|
|
484
|
+
| `links` | Check that every link in the tree lands: dead anchors, dead qualified addresses, wikilinks in frontmatter, drifted manifests, and the package homepage's own addresses. |
|
|
485
|
+
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown). |
|
|
486
|
+
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to. |
|
|
487
|
+
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest). |
|
|
488
|
+
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website). |
|
|
489
|
+
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point. |
|
|
439
490
|
|
|
440
491
|
Every path, pack name and root it needs comes from the consuming repository's
|
|
441
492
|
`package-build.config.yaml`, so the usual invocation takes no arguments beyond
|
|
@@ -469,6 +520,13 @@ in about a second and can gate a commit. An empty or untyped tree **fails**
|
|
|
469
520
|
rather than passing: "every one of nothing is unique" is a vacuous pass, and it
|
|
470
521
|
is exactly what a tree that failed to check out produces.
|
|
471
522
|
|
|
523
|
+
What that guard reports is an **empty walk**, not an empty set of addresses. A
|
|
524
|
+
note may be keyless by design — a homepage carries no `shortcode`, because it is
|
|
525
|
+
addressed by the package rather than by a slug — so a package in
|
|
526
|
+
`publish.site: homepage` mode has a content tree that is populated, correct and
|
|
527
|
+
permanently unkeyed. That tree passes; a tree holding no notes at all still
|
|
528
|
+
fails.
|
|
529
|
+
|
|
472
530
|
### Frontmatter, against the schema its type declares
|
|
473
531
|
|
|
474
532
|
The same command also checks that each note's `sohl:` block is what its **type**
|
|
@@ -512,6 +570,61 @@ does not exist. Removing it was verified output-neutral first: across 1,735
|
|
|
512
570
|
stripped notes, `package compile` produced byte-identical `build/packs-json` and
|
|
513
571
|
the site build byte-identical `site/content`.
|
|
514
572
|
|
|
573
|
+
### The homepage's own links
|
|
574
|
+
|
|
575
|
+
The homepage is the page a reader arrives at, and until #54 it was the one page
|
|
576
|
+
nothing checked. SoHL's landing pointed at `kb/creature/` and `kb/character/`
|
|
577
|
+
from the day those two types merged into `being` — two 404s on the package's
|
|
578
|
+
front page, through every build, because a landing's links went through no
|
|
579
|
+
checker at all.
|
|
580
|
+
|
|
581
|
+
`links` therefore audits a `type: homepage` note as well, and it reads **both**
|
|
582
|
+
halves of it. Of the six homepages authored today four carry every link in the
|
|
583
|
+
body as ordinary markdown and two carry them in `landing:` — and the one whose
|
|
584
|
+
dead links prompted this has an _empty body_. A dead link in a card is exactly
|
|
585
|
+
as broken as one in a paragraph, so `landing.install.url`, every
|
|
586
|
+
`cards….url` / `.href`, the markdown links inside the prose fields (`lead`,
|
|
587
|
+
`closing`, `install.intro`, `install.note`, a card's `description`, a link's
|
|
588
|
+
`note`) and the body's own markdown links are all read.
|
|
589
|
+
|
|
590
|
+
**`url` and `href` are not the same address and are not checked the same way.**
|
|
591
|
+
The theme resolves a `url` against the site with `relURL`, so a package writes
|
|
592
|
+
`kb/rules/` and is served `/sohl/kb/rules/` without naming its own prefix; an
|
|
593
|
+
`href` is an address that is _already_ resolved and is used verbatim, which is
|
|
594
|
+
what `cards.source: sections` fills in. A leading `/` is therefore a defect in a
|
|
595
|
+
`url` — Hugo prefixes it a second time — and correct in an `href`.
|
|
596
|
+
|
|
597
|
+
Four findings, and each one names the form to write instead:
|
|
598
|
+
|
|
599
|
+
| Finding | Why |
|
|
600
|
+
| ---------------------------- | --------------------------------------------------------------------------------- |
|
|
601
|
+
| A **retired content type** | `kb/creature/` when `creature` became `being`. The engine knows what was retired. |
|
|
602
|
+
| A **hardcoded absolute URL** | Into this package's own prefix, or into one a vendored manifest names. |
|
|
603
|
+
| A **root-relative `url:`** | `relURL` prefixes it again. `href:` is exempt — verbatim is what it means. |
|
|
604
|
+
| A **wikilink** | Nothing resolves one here: a homepage is published verbatim in every mode. |
|
|
605
|
+
|
|
606
|
+
That last one is why a homepage does **not** get the wikilink resolution every
|
|
607
|
+
other note body gets. In `homepage` mode the content tree is never walked, so
|
|
608
|
+
there is no index for a wikilink to resolve against — and giving the page one
|
|
609
|
+
would make the mode depend on exactly the machinery its licensing fence exists
|
|
610
|
+
to not build. So a landing addresses the web the way the web does, and a
|
|
611
|
+
wikilink on one is reported rather than resolved.
|
|
612
|
+
|
|
613
|
+
**What is checkable, and what is not.** Only an address into this site is, and
|
|
614
|
+
only against facts the build already holds — the retired-type table and the
|
|
615
|
+
package prefixes a vendored manifest names. Two things are deliberately not
|
|
616
|
+
attempted:
|
|
617
|
+
|
|
618
|
+
- **Whether an external URL answers.** There is no network at build time, and a
|
|
619
|
+
build must not go red because a third party is down.
|
|
620
|
+
- **Whether a live in-site address names a page that exists.** Several surfaces
|
|
621
|
+
a landing routes to are produced by other tools entirely — generated API
|
|
622
|
+
documentation, hand-authored Hugo sections — so this build does not hold the
|
|
623
|
+
set of published pages and would report a working link as dead. A bare
|
|
624
|
+
`https://www.heroiclands.org/<package>/` is left alone for the same reason it
|
|
625
|
+
cannot be improved: a package homepage is in no link manifest, so there is no
|
|
626
|
+
better form to write.
|
|
627
|
+
|
|
515
628
|
## Prose: formatting and markdown
|
|
516
629
|
|
|
517
630
|
```bash
|
|
@@ -778,6 +891,16 @@ rewrites repository-relative links in the developer docs to their published or
|
|
|
778
891
|
GitHub addresses. Neither rewrite can fail a build; an unknown `{@link}` degrades
|
|
779
892
|
to a code span.
|
|
780
893
|
|
|
894
|
+
`symbolMap` is resolved **against the repository root**, not the process cwd, so
|
|
895
|
+
`content-build site` reads the same map whatever directory it was invoked from.
|
|
896
|
+
Leaving it unset is the legitimate empty case — every `{@link}` degrades, and
|
|
897
|
+
nothing is reported. Setting it to a path that cannot be read, cannot be parsed,
|
|
898
|
+
or does not hold a name → page object **fails the build**, naming the file and
|
|
899
|
+
the reason: those were all indistinguishable from "no symbols" until #75, so a
|
|
900
|
+
site could publish 224 dead `{@link}` tags at exit 0. A map that is read reports
|
|
901
|
+
its symbol count at info level, which is the only way to tell a map that loaded
|
|
902
|
+
from one that loaded empty without reading the emitted HTML.
|
|
903
|
+
|
|
781
904
|
A bundle supplies up to two hooks, and their order around the shared work is the
|
|
782
905
|
point:
|
|
783
906
|
|
package/MIGRATING.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
# Migrating to `@heroiclands/package-build` 6.0.0
|
|
2
|
+
|
|
3
|
+
**No configuration change to make, and one build check that may now fail.**
|
|
4
|
+
`packageBuild.manifest.packFolders` is compared against the `packs[]` the build
|
|
5
|
+
derives, and a folder naming a pack the package does not ship is an error.
|
|
6
|
+
|
|
7
|
+
## 1. Check what `package-build manifest` says
|
|
8
|
+
|
|
9
|
+
Nothing to edit up front — run it, and the build names anything wrong:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
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)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Every name in a folder's `packs` must appear in the top-level `packs:` list —
|
|
16
|
+
companions included, since Foundry sees no difference. Delete a name that no
|
|
17
|
+
longer resolves, or correct it. An error stops the manifest being written, so
|
|
18
|
+
nothing half-right reaches the stage.
|
|
19
|
+
|
|
20
|
+
## 2. The advisory needs no action
|
|
21
|
+
|
|
22
|
+
A pack no folder names is a **warning**, and the build continues:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Shipping one pack at the root can be deliberate, so this is not an error. But a
|
|
29
|
+
package that bothered to declare a folder rarely meant to leave one out — that
|
|
30
|
+
is exactly how `HarnMaster-3-FoundryVTT` shipped 1,577 of 1,597 documents loose
|
|
31
|
+
beside its folder. Add the pack to a folder, or leave it and take the advisory.
|
|
32
|
+
|
|
33
|
+
A package that declares no `packFolders` at all is unaffected, and says nothing.
|
|
34
|
+
|
|
35
|
+
## 3. Nothing else
|
|
36
|
+
|
|
37
|
+
- **No configuration key changed**, and no CLI command, flag or exit code beyond
|
|
38
|
+
`manifest` failing on the error above.
|
|
39
|
+
- `writeManifest` takes an optional `configFile`, so a finding names the line it
|
|
40
|
+
is about. Omitting it costs the position, not the finding.
|
|
41
|
+
|
|
1
42
|
# Migrating to `@heroiclands/package-build` 5.0.0
|
|
2
43
|
|
|
3
44
|
**One configuration change: `publish.site` is a mode, not a boolean.** And one
|
package/README.md
CHANGED
|
@@ -255,6 +255,36 @@ comes from.
|
|
|
255
255
|
a companion is only a pack written by another pass rather than one of its own.
|
|
256
256
|
Give each pack the `label` you want Foundry to show.
|
|
257
257
|
|
|
258
|
+
#### `packFolders` is checked against the packs you ship
|
|
259
|
+
|
|
260
|
+
`packFolders` is the one **declared** key that names something the build
|
|
261
|
+
**derives**. Every other declared key states a fact about the package (`title`,
|
|
262
|
+
`socket`, `grid`) or addresses a staged file (`esmodules`, `styles`,
|
|
263
|
+
`languages`) — a staged file being a different relation, answered against the
|
|
264
|
+
stage rather than against configuration. So it is the one place a declaration
|
|
265
|
+
can quietly go stale against a value the build already computed, and it did:
|
|
266
|
+
`HarnMaster-3-FoundryVTT` shipped a folder naming four packs, three of which had
|
|
267
|
+
not existed since its compendium was consolidated, while `items` — 1,577 of
|
|
268
|
+
1,597 documents — sat in no folder at all, with the build reporting nothing.
|
|
269
|
+
|
|
270
|
+
`package-build manifest` now compares the two, descending through nested folders
|
|
271
|
+
(Foundry allows three levels), and reports in the usual
|
|
272
|
+
`file:line:column: severity: message` form:
|
|
273
|
+
|
|
274
|
+
| Finding | Severity | Why |
|
|
275
|
+
| ----------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
276
|
+
| 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 |
|
|
277
|
+
| 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 |
|
|
278
|
+
| no `packFolders` declared at all | nothing | everything at the root is an arrangement, not an omission |
|
|
279
|
+
|
|
280
|
+
An error **stops the write**: a manifest already known to describe packs that do
|
|
281
|
+
not exist should not reach the stage, where the next command would deploy it.
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
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)
|
|
285
|
+
package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
|
|
286
|
+
```
|
|
287
|
+
|
|
258
288
|
`compatibility` and `relationships` are read from the **top level** of the
|
|
259
289
|
shared configuration, not from this section — content-build consumes them
|
|
260
290
|
(`supportedCoreVersion`, and a module'''s `stats.systemVersion`) and the
|
package/bin/content-build.mjs
CHANGED
|
@@ -366,7 +366,8 @@ function lintCommand() {
|
|
|
366
366
|
} else {
|
|
367
367
|
log.info(
|
|
368
368
|
`Addresses and frontmatter are well-formed ` +
|
|
369
|
-
`(${addresses.keys} across
|
|
369
|
+
`(${addresses.keys} address(es) across ` +
|
|
370
|
+
`${addresses.notes} note(s)).`,
|
|
370
371
|
);
|
|
371
372
|
}
|
|
372
373
|
} catch (err) {
|
|
@@ -582,6 +583,7 @@ function linksCommand() {
|
|
|
582
583
|
deadAnchors,
|
|
583
584
|
deadAddresses,
|
|
584
585
|
frontmatterLinks,
|
|
586
|
+
homepageLinks,
|
|
585
587
|
usedManifest,
|
|
586
588
|
} = auditLinks(index);
|
|
587
589
|
|
|
@@ -614,10 +616,24 @@ function linksCommand() {
|
|
|
614
616
|
});
|
|
615
617
|
}
|
|
616
618
|
|
|
619
|
+
// The package homepage. Its addresses are markdown links and
|
|
620
|
+
// `landing:` url/href fields rather than wikilinks — it is
|
|
621
|
+
// published verbatim, so nothing resolves a wikilink on it —
|
|
622
|
+
// and until #54 nothing looked at them at all.
|
|
623
|
+
for (const h of homepageLinks) {
|
|
624
|
+
emitDiagnostic({
|
|
625
|
+
file: h.note.file,
|
|
626
|
+
...positionOfLiteral(h.note.raw, h.text, h.occurrence),
|
|
627
|
+
severity: "error",
|
|
628
|
+
message: `${h.field}: ${h.message}`,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
617
632
|
const failures =
|
|
618
633
|
deadAnchors.length +
|
|
619
634
|
deadAddresses.length +
|
|
620
|
-
frontmatterLinks.length
|
|
635
|
+
frontmatterLinks.length +
|
|
636
|
+
homepageLinks.length;
|
|
621
637
|
if (failures) {
|
|
622
638
|
log.error(
|
|
623
639
|
`${failures} link problem(s) across ${index.notes.length} note(s).`,
|
|
@@ -628,7 +644,8 @@ function linksCommand() {
|
|
|
628
644
|
`${index.notes.length} notes: every anchor link lands ` +
|
|
629
645
|
`and every qualified address resolves ` +
|
|
630
646
|
`(${usedManifest.size} cross-package reference(s) ` +
|
|
631
|
-
`via manifest), no wikilink in frontmatter
|
|
647
|
+
`via manifest), no wikilink in frontmatter, ` +
|
|
648
|
+
`every homepage address resolvable.`,
|
|
632
649
|
);
|
|
633
650
|
}
|
|
634
651
|
} catch (err) {
|