@heroiclands/package-build 6.0.0 → 7.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 +798 -0
- package/CONTENT.md +228 -4
- package/bin/content-build.mjs +196 -10
- package/bin/package-build.mjs +8 -1
- package/config.mjs +25 -3
- package/content-config.mjs +283 -29
- package/engine/address-diff.mjs +290 -0
- package/engine/base-compiler.mjs +25 -0
- package/engine/content-links.mjs +132 -27
- package/engine/content-lint.mjs +23 -2
- package/engine/diagnostics.mjs +61 -1
- package/engine/frontmatter-lint.mjs +22 -0
- package/engine/generate.mjs +10 -5
- package/engine/helpers.mjs +38 -0
- package/engine/homepage.mjs +206 -2
- package/engine/journals.mjs +8 -1
- package/engine/macros.mjs +2 -0
- package/engine/pack-config.mjs +143 -13
- package/engine/prose-lint.mjs +10 -2
- package/engine/scenes.mjs +2 -2
- package/engine/site-build.mjs +74 -19
- package/engine/web-wikilinks.mjs +13 -4
- package/engine/wikilink-syntax.mjs +25 -0
- package/engine/wikilinks.mjs +6 -3
- package/manifest.mjs +37 -2
- package/package.json +5 -3
- package/sohl/actors.mjs +23 -10
- package/sohl/items.mjs +1 -1
- package/types/content-config.d.mts +14 -0
- package/types/engine/address-diff.d.mts +108 -0
- package/types/engine/base-compiler.d.mts +18 -1
- package/types/engine/content-links.d.mts +11 -3
- package/types/engine/content-lint.d.mts +4 -1
- package/types/engine/diagnostics.d.mts +33 -1
- package/types/engine/generate.d.mts +3 -2
- package/types/engine/helpers.d.mts +29 -3
- package/types/engine/homepage.d.mts +117 -0
- package/types/engine/journals.d.mts +7 -1
- package/types/engine/pack-config.d.mts +22 -0
- package/types/engine/prose-lint.d.mts +10 -2
- package/types/engine/site-build.d.mts +28 -3
- package/types/engine/wikilink-syntax.d.mts +24 -0
- package/types/sohl/actors.d.mts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,803 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- a136429: Separate declaring a system from requiring one, and stamp `_stats` per pack
|
|
8
|
+
(#48).
|
|
9
|
+
|
|
10
|
+
A module shipping content for two systems could not say so. The only place to
|
|
11
|
+
state a system version was `relationships.systems`, and that list is a
|
|
12
|
+
**restriction**: Foundry's `supportsSystem` drops a package from any world whose
|
|
13
|
+
system it does not name. So the two needs were in direct conflict — name your
|
|
14
|
+
systems and become unloadable elsewhere, or stay loadable and stamp nothing.
|
|
15
|
+
|
|
16
|
+
**`harn-ensemble` is the case, and it is live.** It declares an `actors-hm3`
|
|
17
|
+
pack and an `actors-sohl` pack, and resolves to:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{ "statsSystemId": null, "statsSystemVersion": null }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Nothing stamped, on content that was certainly built against `hm3 1.6.3` and
|
|
24
|
+
`sohl 0.8.2`. It takes that path deliberately, because declaring the two systems
|
|
25
|
+
would hide the module from every other world — including the ones that want only
|
|
26
|
+
its system-neutral journals pack.
|
|
27
|
+
|
|
28
|
+
**The split.**
|
|
29
|
+
|
|
30
|
+
| | describes | gates |
|
|
31
|
+
| ----------------- | -------------------------------------------- | --------------------------- |
|
|
32
|
+
| `systems:` | which systems this package can stamp against | **nothing** |
|
|
33
|
+
| `requiresSystem:` | — | where the package will load |
|
|
34
|
+
|
|
35
|
+
Naming a system under `systems:` restricts nothing. `requiresSystem` is separate
|
|
36
|
+
and optional, and emits the `relationships.systems` entry Foundry reads —
|
|
37
|
+
_reusing_ the declaration rather than restating it, because
|
|
38
|
+
`stats.systemVersion` sat at `0.6.0` for four releases when a transcription was
|
|
39
|
+
free to disagree with what it copied.
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
systems:
|
|
43
|
+
hm3: { compatibility: { minimum: "1.6.3", verified: "1.6.3" } }
|
|
44
|
+
sohl: { compatibility: { minimum: "0.8.2", verified: "0.8.2" } }
|
|
45
|
+
requiresSystem: null # optional; omitted, the package loads anywhere
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**A pack's `system:` now selects what stamps its documents.** `_stats` was one
|
|
49
|
+
memoised block for the whole package, so every document in every pack was
|
|
50
|
+
stamped identically. It is per pack now: `statsForPack()` resolves the pack's
|
|
51
|
+
declared system through `systems:`, and `BasePackCompiler` exposes it as
|
|
52
|
+
`this.stats`, memoised per instance — one pass, one pack, one system.
|
|
53
|
+
|
|
54
|
+
**`systemId` travels with `systemVersion`.** They are one decision, so where one
|
|
55
|
+
is omitted both are. Stamping a per-pack version against a package-wide id would
|
|
56
|
+
emit `systemId: sohl, systemVersion: 1.6.3` on HM3 documents — a plausible lie,
|
|
57
|
+
which is worse than the missing value #43 fixed, because nothing about it looks
|
|
58
|
+
wrong.
|
|
59
|
+
|
|
60
|
+
**A name that resolves to nothing is an error.** A pack's `system:` must name a
|
|
61
|
+
declared system; `requiresSystem` must too; and with a gate set, a pack naming a
|
|
62
|
+
_different_ system is refused outright — Foundry would hide the whole package
|
|
63
|
+
from any world that pack could have appeared in, so it would ship and be
|
|
64
|
+
unreachable.
|
|
65
|
+
|
|
66
|
+
**Nothing that ships today moves.** All six consumers were resolved before and
|
|
67
|
+
after; none declares a `systems:` block yet, so every pack still falls through to
|
|
68
|
+
the package-wide block it used before:
|
|
69
|
+
|
|
70
|
+
| package | `systems:` | `requiresSystem` | stamps | packs unchanged |
|
|
71
|
+
| -------------------- | ---------: | ---------------- | -------------- | --------------- |
|
|
72
|
+
| `sohl` | 0 | — | `sohl / 0.8.2` | yes |
|
|
73
|
+
| `sohl-thalorna` | 0 | — | `sohl / 0.8.2` | yes |
|
|
74
|
+
| `sohl-kethira-basic` | 0 | — | `sohl / 0.8.2` | yes |
|
|
75
|
+
| `harn-ensemble` | 0 | — | `— / —` | yes |
|
|
76
|
+
| `harn-adventures` | 0 | — | `— / —` | yes |
|
|
77
|
+
| `hm3` | 0 | — | `hm3 / 1.6.3` | yes |
|
|
78
|
+
|
|
79
|
+
**`stats.systemId` and `stats.systemVersion` are refused outright.** Authoring a
|
|
80
|
+
derived value is an error rather than an override, which is the rule this
|
|
81
|
+
configuration already applies elsewhere — and the reason is the same one that let
|
|
82
|
+
`stats.systemVersion` sit at `0.6.0` for four releases: a transcribed copy is
|
|
83
|
+
free to drift from what it copied, and nothing reads a stamped `_stats` until
|
|
84
|
+
something migrates on it. The refusal names the key, its line, and what supplies
|
|
85
|
+
it now.
|
|
86
|
+
|
|
87
|
+
The value still has to reach the validator from the loader, which is the half
|
|
88
|
+
that may read the adjacent `package.json`. It travels under a **symbol**, so the
|
|
89
|
+
channel is not a second, forgeable spelling of the key just refused: a symbol
|
|
90
|
+
cannot be written in YAML and does not appear in `Object.keys`.
|
|
91
|
+
|
|
92
|
+
**`relationships.systems` keeps working, and still answers.** It carries
|
|
93
|
+
`itemCatalog` — a separate concern this split does not replace — so a repository
|
|
94
|
+
using it would otherwise have to restate its compatibility under `systems:`
|
|
95
|
+
purely to keep stamping, which is the duplication the change exists to remove. A
|
|
96
|
+
lone relationship is a declaration as much as a gate, so it derives `systemId`
|
|
97
|
+
too. Several have no single answer and get none.
|
|
98
|
+
|
|
99
|
+
**Every consumer was migrated in the same change.** One line each:
|
|
100
|
+
|
|
101
|
+
```diff
|
|
102
|
+
stats:
|
|
103
|
+
- systemId: sohl
|
|
104
|
+
lastModifiedBy: sohlbuilder00000
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`sohl` and `hm3` are systems and are their own system by construction;
|
|
108
|
+
`sohl-thalorna` and `sohl-kethira-basic` derive it from the single system
|
|
109
|
+
relationship they already declare; `harn-ensemble` and `harn-adventures` declared
|
|
110
|
+
none and were already clean.
|
|
111
|
+
|
|
112
|
+
**Bump**
|
|
113
|
+
|
|
114
|
+
_Major._ `stats.systemId` and `stats.systemVersion` were accepted and are now
|
|
115
|
+
refused, so a configuration that resolved before can fail — which is the
|
|
116
|
+
definition this repository uses. Every HeroicLands consumer is migrated in
|
|
117
|
+
lockstep and verified to stamp exactly what it stamped before, but a consumer
|
|
118
|
+
outside that set must delete the two keys.
|
|
119
|
+
|
|
120
|
+
Part of #57, the third of its three keys that both describe and gate. #56 is
|
|
121
|
+
done, #49 shipped in 6.2.0, and this is #48.
|
|
122
|
+
|
|
123
|
+
### Minor Changes
|
|
124
|
+
|
|
125
|
+
- db40b24: Address another package's landing without naming a host (#87).
|
|
126
|
+
|
|
127
|
+
A link from one package's page to another package's landing had no form but a
|
|
128
|
+
hardcoded absolute URL, and the homepage link check (#54) exempted one — a
|
|
129
|
+
finding whose fix does not exist is noise. `sohl-kethira-basic`'s homepage writes
|
|
130
|
+
`https://www.heroiclands.org/sohl/` twice.
|
|
131
|
+
|
|
132
|
+
**The premise the exemption rested on is true, and does not lead where it looked
|
|
133
|
+
like it led.** A landing is in no link manifest: it compiles to no document and
|
|
134
|
+
is entered in no index. The reading that follows is that nothing can resolve it.
|
|
135
|
+
But a landing's address is not a _note's_ address — it is the **package's**, and
|
|
136
|
+
`PACKAGE_BASE` has recorded where each package is served all along. Consulting it
|
|
137
|
+
walks no tree, reads no manifest and builds no index, which is exactly why the
|
|
138
|
+
mechanism survives the fence: `kethira` and `harnadventures` publish a homepage
|
|
139
|
+
and nothing else, and that mode never walks a content tree.
|
|
140
|
+
|
|
141
|
+
**So the authored form is the absolute URL with the host struck off.** `/sohl/`
|
|
142
|
+
in a body, or `href: /sohl/` in a card. Both were already accepted here — nothing
|
|
143
|
+
had ever named one as the form to use, which is the whole of what was missing.
|
|
144
|
+
|
|
145
|
+
| Field | Cross-package landing | Why |
|
|
146
|
+
| ------- | --------------------- | ----------------------------------------- |
|
|
147
|
+
| body | `[SoHL](/sohl/)` | emitted verbatim, resolved by the browser |
|
|
148
|
+
| `href:` | `/sohl/` | "already resolved, used verbatim" |
|
|
149
|
+
| `url:` | **not expressible** | package-relative by construction |
|
|
150
|
+
|
|
151
|
+
`url:` cannot leave its own package, so a `url:` naming another package's landing
|
|
152
|
+
is now reported with the field as the fix rather than a path — previously it was
|
|
153
|
+
told to "write `/`", which is nonsense arrived at by taking the path after the
|
|
154
|
+
prefix when there is nothing after the prefix.
|
|
155
|
+
|
|
156
|
+
**The base comes from the roster, not from the prefix.** The finding names
|
|
157
|
+
`PACKAGE_BASE[pkg]`, so a package the roster relocates (`/setting/thalorna/`) is
|
|
158
|
+
addressed where it actually is. That is the property the manifest enforces
|
|
159
|
+
everywhere else — the address published is the address emitted — reaching these
|
|
160
|
+
links for the first time.
|
|
161
|
+
|
|
162
|
+
**Roster for landings only.** Widening the package set the other rules read would
|
|
163
|
+
have them offer manifest-based advice about packages no manifest is vendored for.
|
|
164
|
+
An in-site path naming no known package is still left alone: several surfaces a
|
|
165
|
+
landing routes to are built by other tools, and this build does not hold the set
|
|
166
|
+
of published pages.
|
|
167
|
+
|
|
168
|
+
**Measured across every homepage authored today.** All five were run before and
|
|
169
|
+
after, each under its own `package-build.config.yaml`:
|
|
170
|
+
|
|
171
|
+
| Package | Before | After | |
|
|
172
|
+
| ------------------------- | ------ | ----- | ----------------------- |
|
|
173
|
+
| `sohl-kethira-basic` | 0 | **2** | the two the issue names |
|
|
174
|
+
| `sohl-thalorna` | 0 | 0 | |
|
|
175
|
+
| `harn-ensemble` | 0 | 0 | |
|
|
176
|
+
| `harn-adventures` | 0 | 0 | |
|
|
177
|
+
| `HarnMaster-3-FoundryVTT` | 0 | 0 | |
|
|
178
|
+
|
|
179
|
+
Kethira vendors no manifest at all — it is homepage-only — so it is also the
|
|
180
|
+
proof that the roster reaches where an index does not. Applying the fix the
|
|
181
|
+
finding names takes it back to 0.
|
|
182
|
+
|
|
183
|
+
**Sequencing, stated because it decides the bump.** These are hard errors:
|
|
184
|
+
`severity: "error"`, counted into `failures`, `exitCode 1`. By this repository's
|
|
185
|
+
own rule — a new hard error is breaking only if it fails a previously-passing
|
|
186
|
+
consumer — this is minor **only once kethira's two lines are converted**, and
|
|
187
|
+
major if it ships before them. The conversion does not wait on this release:
|
|
188
|
+
`/sohl/` already passes under the current published version, verified, so it can
|
|
189
|
+
land in `sohl-kethira-basic` immediately and independently. It must land first.
|
|
190
|
+
|
|
191
|
+
**Bump**
|
|
192
|
+
|
|
193
|
+
_Minor, not patch, and not major._ Not patch: a check that previously passed a
|
|
194
|
+
page can now fail it. Not major, on the condition above — with kethira converted,
|
|
195
|
+
every homepage authored today passes before and after, and no export, option or
|
|
196
|
+
emitted document changes shape.
|
|
197
|
+
- 35bde43: Locate a configuration error in the file it was written in (#95).
|
|
198
|
+
|
|
199
|
+
Every check across `content-config.mjs` and `config.mjs` — 81 of them — reports
|
|
200
|
+
through one `fail()`, which named the offending key's **dotted path** and
|
|
201
|
+
nothing else. That is a good description and a bad locator: nothing in the line
|
|
202
|
+
is a path an editor can open or a CI annotator can resolve, in a file that runs
|
|
203
|
+
to 400 lines with fourteen sibling entries under `sections:` alone, several of
|
|
204
|
+
them flow-mapped onto one line.
|
|
205
|
+
|
|
206
|
+
The path now rides on the error as a `field`, and the loader that read the file
|
|
207
|
+
resolves it — through `positionOfYamlPath`, the same locator `manifest`'s
|
|
208
|
+
`packFolders` findings already use — so all 81 come out in the
|
|
209
|
+
`file:line:column: severity: message` form every other finding uses, path first:
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
package-build.config.yaml:382:64: error: package-build config: `site.sections.being.descrption` is not a recognized option (expected one of: title, banner, description).
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Located at the boundary, not at the check.** `content-config.mjs` is the leaf
|
|
216
|
+
an `.mjs` configuration imports and performs no I/O, so it attaches the path and
|
|
217
|
+
`configFromData` — which knows the file — formats it. `config.mjs`'s pure
|
|
218
|
+
`resolvePackageBuildConfig` is unchanged for the same reason;
|
|
219
|
+
`loadPackageBuildConfig` is where its findings are located.
|
|
220
|
+
|
|
221
|
+
**Positions are dropped, never guessed.** A required key the file never declared
|
|
222
|
+
has no node of its own, so the position names the **mapping it belongs in**, one
|
|
223
|
+
level up and no further — that entry is a real node and the one the reader must
|
|
224
|
+
edit. A missing _top-level_ key has nothing above it but the document, and an
|
|
225
|
+
`.mjs` configuration has no YAML to resolve a path against at all (parsing
|
|
226
|
+
JavaScript as YAML would resolve some paths to lines that mean nothing). Both
|
|
227
|
+
report `package-build.config.yaml: error: …` — the file, without a line.
|
|
228
|
+
|
|
229
|
+
Both command lines print a located failure unprefixed, since `package-build: `
|
|
230
|
+
and `loglevel`'s `[timestamp] [ERROR]:` occupy exactly the position a parser
|
|
231
|
+
reads the path from.
|
|
232
|
+
|
|
233
|
+
**Additive.** A valid configuration resolves exactly as before; only the text of
|
|
234
|
+
a rejection changes, and it keeps the body it had. `positionOfYamlPath` gains an
|
|
235
|
+
optional `{ key: true }` — report where a key is _declared_ rather than where
|
|
236
|
+
its value sits, which is what a message naming a field wants — and
|
|
237
|
+
`engine/diagnostics` gains `yamlKeyPath`, the one translation between a dotted
|
|
238
|
+
path and a YAML key path.
|
|
239
|
+
- b7ad450: Compile actors without an Item pack of this package's own (#49).
|
|
240
|
+
|
|
241
|
+
The actors pass threw unless the package declared at least one pack of type
|
|
242
|
+
`Item`. An Item pack is system-bound by construction — Foundry requires `system`
|
|
243
|
+
on Item packs and on few others — so the guard asked a deliberately
|
|
244
|
+
system-agnostic module to declare the very thing it exists not to depend on.
|
|
245
|
+
|
|
246
|
+
**This is not hypothetical, and it is not a future case.** `harn-ensemble`
|
|
247
|
+
declares two Actor packs and no Item pack at all in its
|
|
248
|
+
`package-build.config.yaml` today. Run against that configuration, the compiler
|
|
249
|
+
does not start:
|
|
250
|
+
|
|
251
|
+
```text
|
|
252
|
+
packs declared : actors-hm3(Actor), actors-sohl(Actor)
|
|
253
|
+
itemPackJsonDirs : []
|
|
254
|
+
Actors : THREW — Actors compiler requires `itemsSourceDirs` …
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Its 2,512 beings resolve their embedded items — `skill:awar`, `attribute:str`,
|
|
258
|
+
`weapongear:…` — against the `sohl` and `hm3` catalogues through
|
|
259
|
+
`foreignSourceDirs`. The optional mechanism is the one that matters there; the
|
|
260
|
+
mandatory one had nothing to contribute.
|
|
261
|
+
|
|
262
|
+
**The guard did not test what it claimed.** It counted _declared directories_,
|
|
263
|
+
not resolvable items. An Item pack containing no documents satisfied it, while a
|
|
264
|
+
being naming an item nothing defines still failed later — so it neither
|
|
265
|
+
prevented the failure it named nor reported it where it happened. And its
|
|
266
|
+
remedy, "declare at least one pack of type `Item`", is the opposite of the fix
|
|
267
|
+
for a system-agnostic package.
|
|
268
|
+
|
|
269
|
+
**The condition actually cared about was already checked, at the site of the
|
|
270
|
+
mistake.** `resolveEmbedded` reports each unresolved `(type, shortcode)` by
|
|
271
|
+
name, with the being as context, and counts it — and those counts aggregate into
|
|
272
|
+
`totalErrors`, so a package genuinely missing an item still fails the build:
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
Bandit: no predefined item for "skill:awar"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
That is the same line #43 and the frontmatter-lint work drew: a structural
|
|
279
|
+
precondition that is cheap to state is not the condition you care about, and
|
|
280
|
+
reporting where the mistake is beats refusing to start.
|
|
281
|
+
|
|
282
|
+
**Nothing tightens.** `itemsSourceDirs` defaults to `[]` and is otherwise
|
|
283
|
+
unchanged; `foreignSourceDirs` is untouched; `itemPackJsonDirs` already returned
|
|
284
|
+
an empty list for a repository with no Item packs, and only the constructor
|
|
285
|
+
rejected it. A package that declares Item packs behaves exactly as before — the
|
|
286
|
+
1,647 existing tests pass unchanged, with four added for the empty case and for
|
|
287
|
+
the point-of-use error that replaces the guard.
|
|
288
|
+
|
|
289
|
+
**Bump**
|
|
290
|
+
|
|
291
|
+
_Minor, not patch._ No consumer that compiled before fails now — the change only
|
|
292
|
+
removes a refusal — but a configuration that was rejected is now supported, which
|
|
293
|
+
is new surface rather than a repair to existing surface. Not major for the same
|
|
294
|
+
reason: nothing that was accepted stops being accepted.
|
|
295
|
+
|
|
296
|
+
Part of #57, which is the same defect in three places: a key that both describes
|
|
297
|
+
and gates, so the legitimate case cannot be expressed. Here the gate is removed
|
|
298
|
+
and the description — which Item packs this package ships — is left saying only
|
|
299
|
+
that.
|
|
300
|
+
|
|
301
|
+
### Patch Changes
|
|
302
|
+
|
|
303
|
+
- 8aab711: Bump `glob` from 11.1.0 to 13.0.6.
|
|
304
|
+
|
|
305
|
+
**Both majors are changes to glob's command-line program, not its library.**
|
|
306
|
+
|
|
307
|
+
- **12** removed the unsafe `--shell` option, keeping it only on shells where it
|
|
308
|
+
can be implemented safely (the remediation for GHSA-5j98-mcp5-4vw2, whose
|
|
309
|
+
mitigation 11.1 had introduced).
|
|
310
|
+
- **13** moved the CLI out to a separate `glob-bin` package.
|
|
311
|
+
|
|
312
|
+
This repository never invokes that program. The single use of the library is
|
|
313
|
+
`globSync(patterns, { cwd, absolute: true })` in `readMatching`
|
|
314
|
+
(`bin/package-build.mjs`), which is untouched across both majors — no workflow,
|
|
315
|
+
hook, or script calls `glob` from a shell, so the removed binary is not a
|
|
316
|
+
dependency this package had.
|
|
317
|
+
|
|
318
|
+
**Verified rather than assumed.** `globSync` was exercised under 13.0.6 with the
|
|
319
|
+
options `readMatching` actually passes, returning the same absolute paths; the
|
|
320
|
+
repository's 1,642 tests pass unchanged.
|
|
321
|
+
|
|
322
|
+
**Bump**
|
|
323
|
+
|
|
324
|
+
_Patch, not minor._ Nothing in this package's surface moves, and the majors are
|
|
325
|
+
the library's own. Worth noting for a consumer only in one case: a repository
|
|
326
|
+
that installed `glob` transitively through this package and relied on
|
|
327
|
+
`node_modules/.bin/glob` being present must now depend on `glob-bin` directly.
|
|
328
|
+
That was never a supported edge of this package, and no HeroicLands repository
|
|
329
|
+
does it.
|
|
330
|
+
- 8e0778e: Bump `markdown-it` from 14.3.0 to 15.0.0.
|
|
331
|
+
|
|
332
|
+
This package's entire use of the library is `markdownit({ html: true })` and
|
|
333
|
+
`md.render(body)`, at three call sites — `engine/helpers.mjs`,
|
|
334
|
+
`engine/journals.mjs` and `sohl/actors.mjs`. Nothing overrides a renderer rule,
|
|
335
|
+
installs a plugin, or reaches into the parser.
|
|
336
|
+
|
|
337
|
+
**Every breaking change in 15.0.0 lands outside that surface.**
|
|
338
|
+
|
|
339
|
+
| Breaking change | Why it does not reach here |
|
|
340
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
341
|
+
| `linkify-it` → v6: no fuzzy links, no auth check, CJK link termination | `linkify` defaults to `false` and is never enabled, so the linkifier never runs |
|
|
342
|
+
| Package-internal subpath exports (`markdown-it/lib/*`) removed | Only the package root is imported |
|
|
343
|
+
| `validateLink`/`normalizeLink`/`normalizeLinkText` moved to prototype | None is read, assigned, or overridden |
|
|
344
|
+
| `StateBlock#ddIndent` removed | No plugin is installed, `markdown-it-deflist` included |
|
|
345
|
+
| Root now resolves to prebuilt `dist/` rather than raw sources | Import is by package name; the resolved path was never depended on |
|
|
346
|
+
|
|
347
|
+
**Verified rather than assumed.** The same corpus was rendered through 14.3.0
|
|
348
|
+
and 15.0.0 and diffed byte-for-byte, covering exactly the constructs the release
|
|
349
|
+
touched — bare URLs, autolinks, email-shaped text, reference links and their
|
|
350
|
+
definitions, CJK adjacent to a URL, hard line breaks, raw HTML, tables, nested
|
|
351
|
+
lists, and both fenced and indented code. Output is identical. The repository's
|
|
352
|
+
1,642 tests pass unchanged.
|
|
353
|
+
|
|
354
|
+
**Bump**
|
|
355
|
+
|
|
356
|
+
_Patch, not minor._ No export, option, or emitted document changes shape, and no
|
|
357
|
+
behaviour a consumer can observe moves. The version is a major on the library's
|
|
358
|
+
own surface, none of which this package presents onward.
|
|
359
|
+
- 6318722: Add `.github/dependabot.yml`. This repository had none, so nothing proposed a
|
|
360
|
+
dependency update — not npm, not GitHub Actions.
|
|
361
|
+
|
|
362
|
+
That matters more here than in a consumer: this is the toolchain every other
|
|
363
|
+
HeroicLands package builds with, and it is published to npm, so its dependency
|
|
364
|
+
tree reaches every consumer's build and everyone who installs it. The
|
|
365
|
+
consuming repositories are already covered; this producer was the one link in
|
|
366
|
+
the chain nothing watched.
|
|
367
|
+
|
|
368
|
+
`@foundryvtt/foundryvtt-cli` and `classic-level` are each an ungrouped
|
|
369
|
+
single-package entry, ordered ahead of the housekeeping catch-all, for every
|
|
370
|
+
update type including minor and patch. Both sit directly on the compendium
|
|
371
|
+
pack pipeline, and a version bump that silently changes how a pack compiles is
|
|
372
|
+
exactly the failure this repository's byte-level output comparisons exist to
|
|
373
|
+
catch — grouping either into a housekeeping pull request would hide which
|
|
374
|
+
dependency caused a regression.
|
|
375
|
+
|
|
376
|
+
No `ignore` entries: nothing here has a known-bad range to record.
|
|
377
|
+
`typescript` is a direct dependency, but `build.yml`'s "Declaration emit" step
|
|
378
|
+
already gates a breaking bump on every pull request.
|
|
379
|
+
|
|
380
|
+
Closes #100.
|
|
381
|
+
- cd32ea1: Render `[[target|]]` as the target's name on the web, as the packs already do
|
|
382
|
+
(#113).
|
|
383
|
+
|
|
384
|
+
A wikilink with an explicit but empty label produced an **empty anchor** —
|
|
385
|
+
`[](/rules/sohl-shock/)`, a link with no clickable text — silently, through
|
|
386
|
+
every site build:
|
|
387
|
+
|
|
388
|
+
```text
|
|
389
|
+
[[doc-shock]] => [Shock](/rules/sohl-shock/)
|
|
390
|
+
[[doc-shock|]] => [](/rules/sohl-shock/) ← before
|
|
391
|
+
[[doc-shock|]] => [Shock](/rules/sohl-shock/) ← after
|
|
392
|
+
[[#sec|]] => [](#sec) ← before
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**The two resolvers had drawn the same line in two places, differently.**
|
|
396
|
+
`parseWikilink` distinguishes "no label" from "empty label" deliberately, and
|
|
397
|
+
its docstring says so — _"`null` and `""` differ: an author may write
|
|
398
|
+
`[[x|]]`"_. The packs resolver honoured that by testing falsiness. The web
|
|
399
|
+
resolver used `??`, which falls through on `null` only, so `""` survived to the
|
|
400
|
+
output at three sites: the same-page anchor, the resolved link, and the
|
|
401
|
+
unresolved link.
|
|
402
|
+
|
|
403
|
+
That is the third instance of exactly the drift `wikilink-syntax.mjs` was
|
|
404
|
+
created to stop — its module docstring opens on the two copies of the link
|
|
405
|
+
parser having already diverged. The parse was centralised; the _interpretation
|
|
406
|
+
of the parsed parts_ was not, and it drifted in the one case the docstring
|
|
407
|
+
names.
|
|
408
|
+
|
|
409
|
+
**So the reading is stated once.** `authoredLabel()` joins the syntax module and
|
|
410
|
+
both resolvers consult it, rather than each deciding what an empty label means.
|
|
411
|
+
`labelled` is untouched and still separates `[[x]]` from `[[x|]]`, which is what
|
|
412
|
+
#1409 actually depends on — an unlabelled `[[Shock State]]` still shows the
|
|
413
|
+
author's own prose rather than the canonical name.
|
|
414
|
+
|
|
415
|
+
**Why it is worth a release rather than waiting.** `[[x|]]` becomes load-bearing
|
|
416
|
+
under the four-segment address grammar (#59): the pipe is what distinguishes an
|
|
417
|
+
address lookup from an alias lookup, so `[[target|]]` is the canonical way to
|
|
418
|
+
write an address that displays its target's name — and the planned migration
|
|
419
|
+
rewrites every authored address link into that form. Converting the corpus into
|
|
420
|
+
a form that renders an empty anchor would be a corpus-wide regression, so this
|
|
421
|
+
has to land first.
|
|
422
|
+
|
|
423
|
+
**Bump**
|
|
424
|
+
|
|
425
|
+
_Patch._ A defect fix with no new surface. `authoredLabel` is exported because
|
|
426
|
+
both resolvers import it, not as a feature for consumers; no option, address, or
|
|
427
|
+
emitted document changes shape, and no link that renders correctly today renders
|
|
428
|
+
differently after.
|
|
429
|
+
- bc2c5c8: Bump `@types/node` in the lockfile for development tooling maintenance.
|
|
430
|
+
- 1a4d882: Hold `typescript` at major 6 in Dependabot, because TypeScript 7 removes the
|
|
431
|
+
compiler API `coverage.mjs` parses with.
|
|
432
|
+
|
|
433
|
+
Dependabot proposed 6.0.3 → 7.0.2 (#105). It cannot be taken.
|
|
434
|
+
|
|
435
|
+
**TypeScript 7 is the native port, and its npm package no longer ships the
|
|
436
|
+
JavaScript compiler API.** The `"."` export resolves to `lib/version.cjs`, whose
|
|
437
|
+
entire surface is `version` and `versionMajorMinor`.
|
|
438
|
+
|
|
439
|
+
`coverage.mjs` uses that API as a _parser_, not as a compiler: it reads
|
|
440
|
+
localization keys out of a consumer's `src/**/*.{ts,mjs}` by walking a real AST,
|
|
441
|
+
deliberately down one path so JavaScript and TypeScript cannot drift. Under 7.0.2
|
|
442
|
+
the AST **vocabulary** survives behind `typescript/unstable/ast` — `ScriptTarget`
|
|
443
|
+
and every `isX` guard the scan uses — but the three things that actually drive it
|
|
444
|
+
exist nowhere in the JS surface:
|
|
445
|
+
|
|
446
|
+
| Needed by `coverage.mjs` | In 7.0.2 |
|
|
447
|
+
| ------------------------------------------------------------- | ------------------------------------------ |
|
|
448
|
+
| `createSourceFile` | **missing** — only a factory of that name¹ |
|
|
449
|
+
| `forEachChild` | **missing** |
|
|
450
|
+
| `flattenDiagnosticMessageText` | **missing** |
|
|
451
|
+
| `ScriptTarget`, `isStringLiteral`, `isPropertyDeclaration`, … | present, behind `unstable/ast` |
|
|
452
|
+
|
|
453
|
+
¹ 7's `createSourceFile` assembles a SourceFile from statements already parsed.
|
|
454
|
+
It is not a parser.
|
|
455
|
+
|
|
456
|
+
**The gate this file predicted would catch it did not.** The previous comment in
|
|
457
|
+
`dependabot.yml` recorded no ignore entry on the reasoning that `build.yml`'s
|
|
458
|
+
"Declaration emit" step already guards a breaking `typescript` bump. That step
|
|
459
|
+
passes clean under 7.0.2 — `tsc` still emits every `.d.mts`. What failed was
|
|
460
|
+
`npm test`: 11 failures in `tests/coverage.test.ts`, all
|
|
461
|
+
`TypeError: Cannot read properties of undefined (reading 'Latest')`. A dependency
|
|
462
|
+
can be load-bearing in two unrelated ways at once, and the file named only one of
|
|
463
|
+
them. That correction is now written where the wrong prediction was.
|
|
464
|
+
|
|
465
|
+
**Why an ignore rather than a migration.** Parsing in 7 lives in the Go binary,
|
|
466
|
+
reachable only through `unstable/sync`'s Project/Program API. Adopting it would
|
|
467
|
+
put a subprocess and a virtual filesystem inside a module whose stated contract
|
|
468
|
+
is "everything here is pure — source text in, references or findings out", in
|
|
469
|
+
exchange for an API whose own export path says `unstable`. Acorn is not a
|
|
470
|
+
substitute either: the default scan glob is `src/**/*.{ts,mjs}`, and the largest
|
|
471
|
+
consumer's sources are TypeScript.
|
|
472
|
+
|
|
473
|
+
That migration is worth doing when the API stabilises. It is not a dependency
|
|
474
|
+
bump, and it should not arrive as one.
|
|
475
|
+
|
|
476
|
+
**Scope of the hold.** Majors only — minor and patch releases within 6 still
|
|
477
|
+
arrive on the weekly schedule. The entry names the two conditions that lift it:
|
|
478
|
+
a stable in-process parse entry point, or a `coverage.mjs` that no longer needs
|
|
479
|
+
one.
|
|
480
|
+
|
|
481
|
+
**Bump**
|
|
482
|
+
|
|
483
|
+
_Patch._ Nothing shipped changes. `.github/dependabot.yml` is this repository's
|
|
484
|
+
own automation and sits outside `files`; the `typescript` range in
|
|
485
|
+
`package.json` is untouched, because `^6.0.3` already excludes 7 — the entry
|
|
486
|
+
stops the pull request being reopened, it does not change what resolves.
|
|
487
|
+
- f3167c3: Read the changesets action's `pr-number` output, so the step that marks the
|
|
488
|
+
Version Packages pull request stops being dead code (#84).
|
|
489
|
+
|
|
490
|
+
`release.yml` pins `changesets/action@v2` but read v1's `pullRequestNumber`. An
|
|
491
|
+
unset output evaluates to the empty string rather than erroring, so the guard was
|
|
492
|
+
`'' != ''` — always false. The step has never run once.
|
|
493
|
+
|
|
494
|
+
**The failure was silent by construction, which is why it survived three
|
|
495
|
+
releases.** A misspelled output does not fail a workflow; it disappears. `v3.4.0`,
|
|
496
|
+
`v4.0.0` and `v5.0.0` were all cut with this step skipped, and every run reported
|
|
497
|
+
green.
|
|
498
|
+
|
|
499
|
+
**Only one of the three names actually moved.** Checked against v2's own
|
|
500
|
+
`action.yml` rather than against the assumption that v2 kebab-cased everything:
|
|
501
|
+
|
|
502
|
+
| v1 | v2 | Used here |
|
|
503
|
+
| ------------------- | -------------------- | ------------------------------------- |
|
|
504
|
+
| `pullRequestNumber` | `pr-number` | yes — the two lines this change fixes |
|
|
505
|
+
| `publishedPackages` | `published-packages` | no |
|
|
506
|
+
| `hasChangesets` | `has-changesets` | no |
|
|
507
|
+
| `published` | `published` | yes — **unchanged**, and left alone |
|
|
508
|
+
|
|
509
|
+
`published` is the one name v2 kept. A blanket kebab-case sweep of this file —
|
|
510
|
+
the obvious reading of "rename the v1 outputs" — would have broken the one step
|
|
511
|
+
that was working.
|
|
512
|
+
|
|
513
|
+
**Bracket notation, not `outputs.pr-number`.** A hyphen is the subtraction
|
|
514
|
+
operator in an Actions expression, so the dotted form parses as
|
|
515
|
+
`outputs.pr - number`: a second silent-ish defect sitting directly behind the
|
|
516
|
+
first. `steps.changesets.outputs['pr-number']` is the form that means what it
|
|
517
|
+
reads as.
|
|
518
|
+
|
|
519
|
+
**What this repairs.** The Version Packages pull request is opened by
|
|
520
|
+
`GITHUB_TOKEN`, and GitHub deliberately starts no workflow runs from that token,
|
|
521
|
+
so the required `Changeset declared` context never reports on it. This step
|
|
522
|
+
exists to post that status. With it inert, every Version Packages pull request
|
|
523
|
+
has needed the check waived or force-merged by hand.
|
|
524
|
+
|
|
525
|
+
**Not yet demonstrated running, and stated rather than glossed.** The acceptance
|
|
526
|
+
criterion asks for a run where the step is not `skipped`, and that can only
|
|
527
|
+
happen on `main`, on the next release that opens a Version Packages pull request
|
|
528
|
+
— this change cannot produce one from a branch. The expression is verified by
|
|
529
|
+
parsing the workflow and by v2's manifest; the live proof arrives with the next
|
|
530
|
+
bump.
|
|
531
|
+
|
|
532
|
+
**Sibling repositories are fixed individually, not swept from here.** The same
|
|
533
|
+
defect is open on `harn-ensemble` (#13) and `HarnMaster-3-FoundryVTT` (#427),
|
|
534
|
+
where it is more severe — there the misspelling gates the release itself, and no
|
|
535
|
+
release is ever cut. Each repository owns its own copy of `release.yml`; this one
|
|
536
|
+
is not a reusable workflow. Turning it into one is a real improvement and a
|
|
537
|
+
separate change, and folding it into a two-line fix would put a shared release
|
|
538
|
+
pipeline into production on the back of a typo correction.
|
|
539
|
+
|
|
540
|
+
**Bump**
|
|
541
|
+
|
|
542
|
+
_Patch, not minor._ Nothing this package exports, emits, or documents for a
|
|
543
|
+
consumer changes. The file is this repository's own release plumbing, and it is
|
|
544
|
+
not shipped: `.github` is outside `files`.
|
|
545
|
+
- 2bdc792: Make `lint:markdown` pass, and run it in CI (#92).
|
|
546
|
+
|
|
547
|
+
This repository provides `content-build markdown` and was the one repository that
|
|
548
|
+
never ran it. The script existed, failed, and was executed by nothing — no
|
|
549
|
+
aggregate `lint` script, and no workflow naming it.
|
|
550
|
+
|
|
551
|
+
**The finding count in the issue is wrong, and the correction changes the
|
|
552
|
+
decision.** 117 is what `npx markdownlint-cli2 CHANGELOG-content-build.md`
|
|
553
|
+
reports — markdownlint's _default_ rule set, which this toolchain deliberately
|
|
554
|
+
turns off (`default: false`, then each rule enabled by name). Under the rules the
|
|
555
|
+
repository actually uses, `npm run lint:markdown` reports **three**: two
|
|
556
|
+
`MD001` heading skips and one `MD034` bare URL. The issue's "fix it — 117
|
|
557
|
+
mechanical findings" and "exclude it — 117 is too many to fix" were both
|
|
558
|
+
arguments about a number that was never the repository's.
|
|
559
|
+
|
|
560
|
+
**Excluded anyway, and not because three is still too many.**
|
|
561
|
+
`CHANGELOG-content-build.md` is the published changelog of
|
|
562
|
+
`@heroiclands/content-build` — a **deprecated repository**, absorbed into this
|
|
563
|
+
one at 3.0.0 (#32). It is frozen, not merely generated: exactly one commit has
|
|
564
|
+
ever touched it, nothing regenerates it, and it ships only because `files` lists
|
|
565
|
+
it. Its three findings are facts about what content-build published. Rewriting
|
|
566
|
+
them would edit a historical record to satisfy a rule about prose nobody will
|
|
567
|
+
write again, and would put a style-only commit in the blame of a file whose whole
|
|
568
|
+
value is being what was published. It is the same class as `CHANGELOG.md`, which
|
|
569
|
+
the shared default already ignores for the weaker reason that the next release
|
|
570
|
+
rewrites it.
|
|
571
|
+
|
|
572
|
+
**Declared locally, so no consumer moves.** The exclusion is a new
|
|
573
|
+
`.markdownlint-cli2.jsonc` in this repository, not an entry added to the shared
|
|
574
|
+
`MARKDOWN_IGNORES`. One repository's retired filename does not belong in
|
|
575
|
+
configuration six repositories consume, and the shared rule set is unchanged for
|
|
576
|
+
all of them.
|
|
577
|
+
|
|
578
|
+
**What that file had to get right, and what it documents.** `content-build
|
|
579
|
+
markdown` passes the shared rules as markdownlint-cli2's `optionsDefault`, and a
|
|
580
|
+
consumer file merges over them **key by key, each key wholesale**:
|
|
581
|
+
|
|
582
|
+
| Declared locally | Effect on the shared default |
|
|
583
|
+
| ---------------- | --------------------------------------------------------------------- |
|
|
584
|
+
| only `ignores` | rule set survives intact — `default: false` and every per-rule option |
|
|
585
|
+
| `ignores` | **replaces** `MARKDOWN_IGNORES`; it does not extend it |
|
|
586
|
+
|
|
587
|
+
So the local file restates `CHANGELOG.md`. Verified rather than assumed — with
|
|
588
|
+
that entry dropped, `lint:markdown` reports ten findings in `CHANGELOG.md`; with
|
|
589
|
+
a probe file present, `MD049` still fires with its shared `underscore` option
|
|
590
|
+
while `MD013` stays silent, which is what proves the rule set was not replaced.
|
|
591
|
+
|
|
592
|
+
`engine/prose-lint.mjs` said a consumer config "replaces it", which is the
|
|
593
|
+
reading that would send the next person to add the ignore to the shared default
|
|
594
|
+
or to a full config copy. Its docstring now states the key-by-key rule and the
|
|
595
|
+
`ignores` trap by name.
|
|
596
|
+
|
|
597
|
+
**Wired as a separate CI step, not folded into the chain.** `npm run lint` is
|
|
598
|
+
added for running both checks locally in one command, but `build.yml` gets a
|
|
599
|
+
`Markdown` step of its own beside `Formatting`. The aggregate chains with `&&`,
|
|
600
|
+
so a formatting failure would short-circuit it and hide every markdown finding
|
|
601
|
+
behind it; as two steps the failing one is named in the checks UI.
|
|
602
|
+
`lint:markdown:fix` is added too — the issue referred to it, and it did not exist.
|
|
603
|
+
|
|
604
|
+
**The exclusion cannot rot silently.** If a future markdownlint-cli2 bump changes
|
|
605
|
+
those merge semantics, the restated `CHANGELOG.md` entry stops applying and its
|
|
606
|
+
ten findings reappear — in the CI step this change adds. The guard is checked by
|
|
607
|
+
the thing it guards.
|
|
608
|
+
|
|
609
|
+
**Bump**
|
|
610
|
+
|
|
611
|
+
_Patch._ Nothing a consumer imports, calls, or configures changes behaviour. The
|
|
612
|
+
one shipped file touched is a docstring in `engine/prose-lint.mjs`, which reaches
|
|
613
|
+
consumers through the emitted declarations; everything else — the workflow, the
|
|
614
|
+
local lint config, the scripts — is this repository's own plumbing and is outside
|
|
615
|
+
`files`.
|
|
616
|
+
|
|
617
|
+
## 6.1.0
|
|
618
|
+
|
|
619
|
+
### Minor Changes
|
|
620
|
+
|
|
621
|
+
- dc424d7: **`content-build addresses diff` reports every published address a build has
|
|
622
|
+
stopped publishing, and tells a rename from a removal** (#66).
|
|
623
|
+
|
|
624
|
+
A package's `(type, shortcode)` addresses are a published interface — every
|
|
625
|
+
satellite declaring `itemCatalog: true` assembles its beings out of them — but
|
|
626
|
+
nothing compared one release's addresses against the next, so renaming a
|
|
627
|
+
shortcode cost nothing and produced no signal. The check that got made instead
|
|
628
|
+
was a repository-local grep, which cannot see the other repositories and reports
|
|
629
|
+
the reassuring answer. `sohl` renamed `weapongear:Tabri` to `weapongear:Taburi`
|
|
630
|
+
two days after the `v0.8.2` tag both satellites pin, stating that nothing
|
|
631
|
+
referenced the old value; five lookups across two repositories do, and they fail
|
|
632
|
+
the moment either pin moves.
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
gh release download v0.8.2 -p system.zip -D build/baseline
|
|
636
|
+
npx content-build addresses diff --from build/baseline/system.zip
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Run against `sohl` today that reports 20 findings — 8 renames and 12
|
|
640
|
+
withdrawals — including the `Tabri` one, at the line of the note that made it:
|
|
641
|
+
|
|
642
|
+
```text
|
|
643
|
+
assets/content/Weapons/Melee/Taburi.md:12:1: warning: since sohl@0.8.2, weapongear:Tabri is no longer published; the same document (s5D6QJbw7ZbETxdN) is now published as weapongear:Taburi. Every package that resolves weapongear:Tabri breaks when it moves past sohl@0.8.2
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**A rename is told from a removal by the document id, which is an identity match
|
|
647
|
+
rather than an inference.** A note authors its `_id` in frontmatter and it is not
|
|
648
|
+
derived from the shortcode, so it survives a rename. Where the id is published
|
|
649
|
+
under no address at all, the finding says only that — _withdrawn_, naming no
|
|
650
|
+
successor. A split, a deletion and a merge are indistinguishable at that point,
|
|
651
|
+
and a "did you mean" guessed from string similarity would send the reader to the
|
|
652
|
+
wrong fix.
|
|
653
|
+
|
|
654
|
+
Both findings are warnings and neither fails a build: retiring content is
|
|
655
|
+
legitimate, and so is renaming — the shortcode charset rule forces some. What a
|
|
656
|
+
rename must not do is happen in silence. `--strict` reports both as errors and
|
|
657
|
+
exits non-zero, for a release workflow that wants a gate.
|
|
658
|
+
|
|
659
|
+
Additive: nothing runs unless the command is invoked, and no existing behaviour
|
|
660
|
+
changed.
|
|
661
|
+
- f28f653: A section can now describe itself: `site.sections` and `site.readmeSections`
|
|
662
|
+
take a `description`, and it reaches the generated `_index.md` (#91).
|
|
663
|
+
|
|
664
|
+
A generated section landing is the only place a section can speak — a content
|
|
665
|
+
package authors no `_index.md` for `weapongear` or `affliction`, so the file the
|
|
666
|
+
theme reads is the one this build writes. Its vocabulary was two keys, and
|
|
667
|
+
`partials/hero-banner.html` has always rendered `description` as the hero
|
|
668
|
+
standfirst, so every generated landing rendered a heading with no standfirst and
|
|
669
|
+
could not be given one by any consumer, at any level.
|
|
670
|
+
|
|
671
|
+
**The pair was the defect, not a passthrough.** The vocabulary lived in two
|
|
672
|
+
places that had to be kept in step by hand: the schema that admits a key, and
|
|
673
|
+
the two writers that each transcribed `title` and `banner` by name. A key added
|
|
674
|
+
to the schema alone validated cleanly and then reached no page. So the writers
|
|
675
|
+
stop naming keys and emit what the section resolved to, `title` first, and the
|
|
676
|
+
schema is now the single place a section's vocabulary is decided.
|
|
677
|
+
|
|
678
|
+
**The bound stays.** An unrecognised key under a section is refused at config
|
|
679
|
+
load, by name, exactly as before — `site.sections.affliction.descrption is not a
|
|
680
|
+
recognized option (expected one of: title, banner, description)`. Passing
|
|
681
|
+
sections through unvalidated the way `site.landing` is was weighed and refused:
|
|
682
|
+
`landing` is written once, for the mount, in one landing template's vocabulary,
|
|
683
|
+
where a section entry is written fourteen to twenty times per build against a
|
|
684
|
+
contract every package shares. Unbounded, a mistyped `descrption:` would publish
|
|
685
|
+
into front matter, be read by nobody, and say nothing to anyone — which is the
|
|
686
|
+
failure this issue was filed about, one step downstream where no build can see
|
|
687
|
+
it.
|
|
688
|
+
|
|
689
|
+
Additive. `description` is optional and no consuming package declares one — none
|
|
690
|
+
could, since the loader has always refused it — so every generated landing is
|
|
691
|
+
byte-identical, verified across all six consuming packages.
|
|
692
|
+
- 43a9de4: Refuse `name`, `shortcode` and `id` on a `type: homepage` note (#53).
|
|
693
|
+
|
|
694
|
+
A note's URL derives from `name.full` and its identity from `(type, shortcode)`.
|
|
695
|
+
The homepage is the one page for which neither holds — it publishes at
|
|
696
|
+
`/<package>/`, fixed by the package id — so an author fluent in the conventions
|
|
697
|
+
writes them there expecting exactly what they do everywhere else, and gets none
|
|
698
|
+
of it. Until now `content-build lint` ignored all three: a homepage carrying
|
|
699
|
+
`shortcode`, `id` and `name.full` passed at exit 0.
|
|
700
|
+
|
|
701
|
+
**They were never inert, which is why ignoring them was the wrong answer.** A
|
|
702
|
+
`shortcode` puts the note in the address index and in the `dataview` link
|
|
703
|
+
universe, so `[[homepage-<shortcode>]]` resolves _green_ — to `homepage/<slug>/`,
|
|
704
|
+
an address derived from `name.full` and published by nothing, because a homepage
|
|
705
|
+
is written to `_index.md` at the package root. A build that reports a live link
|
|
706
|
+
to a 404 is worse than one that says nothing.
|
|
707
|
+
|
|
708
|
+
**The inflated address tally is the same defect, not a second one.** The same
|
|
709
|
+
note was counted as an address it does not publish, so the lint and the link
|
|
710
|
+
manifest disagreed about what the package ships: SoHL's tree reports
|
|
711
|
+
`1607 address(es) across 1607 note(s)` with a `shortcode` on its landing and
|
|
712
|
+
`1606 address(es) across 1607 note(s)` without one. The tally is only ever
|
|
713
|
+
printed on a clean run, so refusing the field is what makes the count honest —
|
|
714
|
+
`lintContentTree` needed no change, and got none.
|
|
715
|
+
|
|
716
|
+
**A named class, not an allow-list, and that boundary is the decision.** An
|
|
717
|
+
unknown top-level key is deliberately still accepted. A homepage's frontmatter is
|
|
718
|
+
emitted into the published page, so an unrecognised key is a Hugo or theme
|
|
719
|
+
parameter this build has never heard of and has no standing to reject, and a
|
|
720
|
+
closed list would make every new theme parameter wait on a package-build release.
|
|
721
|
+
What is refused is the class that makes a false claim about _where this page is_.
|
|
722
|
+
`aliases` is not in it either — it is already dropped from every emitted page, so
|
|
723
|
+
authoring one here is the same no-op it is anywhere else. This departs from the
|
|
724
|
+
issue's third acceptance criterion, deliberately: over-strictness here breaks
|
|
725
|
+
authoring on a page whose whole frontmatter is pass-through.
|
|
726
|
+
|
|
727
|
+
**Where it fires: `content-build lint` only.** This is a frontmatter-schema rule,
|
|
728
|
+
and `content-build site` runs none of them; wiring one type's field rule in there
|
|
729
|
+
would have the site build refuse `shortcode` on a homepage while accepting
|
|
730
|
+
`weight: heavy` on a weapon. The gap that leaves is `HarnMaster-3-FoundryVTT`,
|
|
731
|
+
which runs no `content-build lint` at all and so receives no frontmatter finding
|
|
732
|
+
of any kind — a missing script in that repository rather than a rule to duplicate
|
|
733
|
+
one at a time.
|
|
734
|
+
|
|
735
|
+
Each finding is located at the offending key and says what the field would have
|
|
736
|
+
decided:
|
|
737
|
+
|
|
738
|
+
```text
|
|
739
|
+
assets/content/homepage.md:26:1: error: `shortcode` decides nothing on a `type: homepage` note: this page's address is the package's own, `/<package>/`, fixed by the package id. It is not ignored either — it puts the note in the address index, so `[[homepage-<shortcode>]]` resolves to a page the site build never writes. Delete it
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Minor rather than major, measured rather than assumed.** A new hard error is
|
|
743
|
+
breaking only if it fails a previously-passing consumer. All six HeroicLands
|
|
744
|
+
content packages were linted at their default branch, before and after — `sohl`,
|
|
745
|
+
`hm3`, `thalorna`, `kethira`, `harnensemble` and `harnadventures` — and every one
|
|
746
|
+
produces byte-identical findings and the same exit code with the rule as without
|
|
747
|
+
it. None of the six authors any of the three fields on its homepage.
|
|
748
|
+
- d1166d0: Require exactly one `type: homepage` note per package (#52).
|
|
749
|
+
|
|
750
|
+
Every package is reachable at `/<package>/` and what a reader finds there is one
|
|
751
|
+
authored note in its content tree — but nothing required a package to have one,
|
|
752
|
+
so the failure mode of the whole arrangement was a package that builds green and
|
|
753
|
+
serves nothing at its own address.
|
|
754
|
+
|
|
755
|
+
**Zero and two are the same defect, at the same severity.** Neither is a warning,
|
|
756
|
+
because a build that proceeds past either publishes the wrong front page while
|
|
757
|
+
reporting success — which is exactly what a warning tolerates.
|
|
758
|
+
|
|
759
|
+
| Count | What ships |
|
|
760
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
761
|
+
| **Zero** | Nothing at `/<package>/`, silently: the site build reports `wrote 0 homepage(s)` and exits 0. |
|
|
762
|
+
| **Two** | A page nobody chose. Both are written to the same `_index.md`, so the last one walked wins — the front page decided by _filename_, on a type whose point is frontmatter routing. |
|
|
763
|
+
|
|
764
|
+
**It fires in `content-build lint` and in `content-build site`, because neither
|
|
765
|
+
one reaches every package.** `HarnMaster-3-FoundryVTT` runs `site` and no `lint`;
|
|
766
|
+
`sohl-thalorna` runs `lint` and its own site builder. A rule in one of them is a
|
|
767
|
+
rule two of the six packages do not have. Both call the same function, so this is
|
|
768
|
+
one rule with two call sites rather than two rules that can drift. In the site
|
|
769
|
+
build it runs _before the output tree is cleared_, so a failing gate cannot
|
|
770
|
+
destroy a good site to report a bad tree.
|
|
771
|
+
|
|
772
|
+
**It does not vary by `publish.site`.** That setting chooses whether the
|
|
773
|
+
_content_ surfaces are published; the homepage is the floor beneath both modes.
|
|
774
|
+
The lint call site reads no `site:` block at all, so it could not vary by mode
|
|
775
|
+
even if the rule wanted to.
|
|
776
|
+
|
|
777
|
+
**Zero has no file to name, and none is invented.** The locator is the content
|
|
778
|
+
root — a real path, and the directory the note has to be added to — with no line
|
|
779
|
+
and no column, as the diagnostic rules require. Two is reported once per note,
|
|
780
|
+
located at its own `type:` value and naming the other, because each note is a
|
|
781
|
+
place an author has to open and edit:
|
|
782
|
+
|
|
783
|
+
```text
|
|
784
|
+
assets/content: error: holds no `type: homepage` note, so package "sohl" publishes nothing at its own address /sohl/ — a package's front page is one authored note in this tree, routed by `type:` rather than by filename
|
|
785
|
+
assets/content/homepage.md:3:7: error: duplicate `type: homepage` note, also declared by assets/content/Landing.md; a package has one front page, at /sohl/, and every homepage is written to the same `_index.md` — so the one the walk reaches last silently overwrites the rest
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
**Minor rather than major, measured rather than assumed.** A new hard error is
|
|
789
|
+
breaking only if it fails a previously-passing consumer. All six HeroicLands
|
|
790
|
+
content packages were run at their default branch, before and after: `sohl`,
|
|
791
|
+
`hm3`, `thalorna`, `kethira`, `harnensemble` and `harnadventures` each carry
|
|
792
|
+
exactly one homepage note, and every one produces byte-identical findings and the
|
|
793
|
+
same exit code with the check as without it.
|
|
794
|
+
|
|
795
|
+
The issue's sequencing — ship it inert, flip it to an error later — was written
|
|
796
|
+
when the count was ~45 repositories and two homepages existed only in unmerged
|
|
797
|
+
pull requests. Both have since merged, the real count is six, and every one
|
|
798
|
+
passes today, so the warning window would protect nobody and the flip would be a
|
|
799
|
+
second pull request for no reason.
|
|
800
|
+
|
|
3
801
|
## 6.0.0
|
|
4
802
|
|
|
5
803
|
### Major Changes
|