@heroiclands/package-build 13.0.0 → 15.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 +361 -0
- package/CONTENT.md +193 -43
- package/MIGRATING.md +196 -5
- package/content-config.mjs +99 -61
- package/docs/content-format.md +28 -8
- package/engine/content-address.mjs +11 -19
- package/engine/field-reference.mjs +29 -0
- package/engine/field-spec.mjs +25 -0
- package/engine/frontmatter-lint.mjs +99 -32
- package/engine/helpers.mjs +38 -12
- package/engine/homepage.mjs +9 -4
- package/engine/item-registry.mjs +4 -1
- package/engine/macros.mjs +3 -1
- package/engine/manifest-emit.mjs +4 -7
- package/engine/note-vocabulary.mjs +34 -76
- package/engine/site-build.mjs +20 -14
- package/engine/system-block.mjs +29 -3
- package/package.json +1 -1
- package/sohl/actors.mjs +6 -3
- package/sohl/item-fields.mjs +6 -0
- package/sohl/items.mjs +4 -1
- package/types/content-config.d.mts +25 -35
- package/types/engine/content-address.d.mts +7 -14
- package/types/engine/field-spec.d.mts +53 -0
- package/types/engine/helpers.d.mts +34 -12
- package/types/engine/homepage.d.mts +8 -4
- package/types/engine/manifest-emit.d.mts +3 -9
- package/types/engine/note-vocabulary.d.mts +17 -47
- package/types/engine/site-build.d.mts +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,366 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 15.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 5c16c02: Let a note's art path say "unset" and "blank on purpose" with two different values.
|
|
8
|
+
|
|
9
|
+
`resolveImg` opened with `if (!raw) return ""`, and every caller then applied its
|
|
10
|
+
own default to the result with `||` — `resolveImg(fm.img) || itemArt(type)`. So
|
|
11
|
+
`""`, `null` and an absent key were one case: all three compiled to the type's
|
|
12
|
+
default art, and a note had no way to say _ship no image_ at all.
|
|
13
|
+
|
|
14
|
+
They are now three values with two meanings, the convention the project already
|
|
15
|
+
holds for an optional "not specified" DataModel string (`nullable, initial: null`,
|
|
16
|
+
so "unset" is one honest value rather than two):
|
|
17
|
+
|
|
18
|
+
| a note writes | it means | it compiles with |
|
|
19
|
+
| -------------- | ------------------------------------ | ---------------- |
|
|
20
|
+
| nothing at all | _unset_ — name me no art | the type default |
|
|
21
|
+
| `img: null` | the same thing, said out loud | the type default |
|
|
22
|
+
| `img: ""` | _blank on purpose_ — I want no image | no image |
|
|
23
|
+
|
|
24
|
+
**`resolveImg` returns `string | null`.** `null` for an unset path, `""` for a
|
|
25
|
+
deliberate blank, the translation half unchanged. Every caller pairs its default
|
|
26
|
+
with **nullish** coalescing: `sohl/items.mjs`, three in `sohl/actors.mjs` (`img`,
|
|
27
|
+
`portrait`, and the prototype token's `texture.src`), and `engine/macros.mjs`.
|
|
28
|
+
Not `||` — that collapses a deliberate blank back into the default and takes the
|
|
29
|
+
distinction away again, and it does so silently, because `""` is falsy.
|
|
30
|
+
`itemArt()` is unaffected: a registry entry with no art throws before the
|
|
31
|
+
translation, so its result is never the unset case.
|
|
32
|
+
|
|
33
|
+
**The default-art seam is a documented extension point, so this is the substance
|
|
34
|
+
of the change, not a detail.** A consuming repository that pairs art with its own
|
|
35
|
+
`itemBuilders` entry, or calls `resolveImg` from a builder of its own, gets the
|
|
36
|
+
new reading of `""` whether or not it asked for it — which is why the sweeps have
|
|
37
|
+
to come first. `sohl-thalorna` swept forty-five `img: ""` notes ahead of this
|
|
38
|
+
release (HeroicLands/sohl-thalorna#134) and `sohl-kethira-basic` eleven
|
|
39
|
+
`portrait: ""` beings (HeroicLands/sohl-kethira-basic#81); `sohl` authors neither,
|
|
40
|
+
and its 3,125 compiled documents are byte-identical across the change.
|
|
41
|
+
|
|
42
|
+
**Both art fields, because both go through `resolveImg`.** A being carries `img`
|
|
43
|
+
(its token art) and `portrait` (its sheet portrait) independently; `portrait` is
|
|
44
|
+
not a variant spelling of `img`, and the rule belongs to the translator rather
|
|
45
|
+
than to one of the keys reaching it. This is not theoretical: `sohl-kethira-basic`
|
|
46
|
+
writes `portrait: ""` on eleven beings and `img: ""` on none, so a change — or a
|
|
47
|
+
guard — keyed on `img` alone would have called that tree clean and dropped every
|
|
48
|
+
one of those portraits.
|
|
49
|
+
|
|
50
|
+
**A warning for the old spelling.** The frontmatter lint reports `img: ""` and
|
|
51
|
+
`portrait: ""` — in either authoring position — as the meaning-change they are, on
|
|
52
|
+
the pattern the `package:` and retired-alias sweeps set: a warning, because the
|
|
53
|
+
note still compiles, to a document that is merely iconless.
|
|
54
|
+
|
|
55
|
+
**`title` is deliberately not on this rule.** It reads as a general rule about
|
|
56
|
+
optional strings and it is not: on a `type: affiliation` note `title` is
|
|
57
|
+
_simultaneously_ a declared item field whose default is `""`
|
|
58
|
+
(`sohl/item-fields.mjs`), resolved from the very same shared top-level key the
|
|
59
|
+
site emitter reads as the page title. `title: null` therefore does not fall back —
|
|
60
|
+
it stringifies, and the compiled document ships the literal `"null"`. A `title` a
|
|
61
|
+
note does not want is written by omitting the key; the emitter's `fm.title ?? name`
|
|
62
|
+
is already correct and is untouched. The lint guard is `img`'s alone for the same
|
|
63
|
+
reason.
|
|
64
|
+
|
|
65
|
+
Delivers the `img` half of #218. The `title` half — the collision above — stays
|
|
66
|
+
open.
|
|
67
|
+
- 63dfcae: **A note's own `title` no longer fills an affiliation's `system.title`** (#218).
|
|
68
|
+
|
|
69
|
+
The two were never the same quantity. A note's top-level `title` is _the title
|
|
70
|
+
of the note_ — the heading its page is published under, which the site emitter
|
|
71
|
+
reads. An `affiliation` item's `system.title` is _the style of address the office
|
|
72
|
+
carries_ — Ajaw, Warden, a person's style within the body. They collided only in
|
|
73
|
+
spelling, and field resolution's third step, the shared top-level property, fed
|
|
74
|
+
the second from the first.
|
|
75
|
+
|
|
76
|
+
That step also answers **without** applying the field's default — only the
|
|
77
|
+
in-block step does — so an authored `title: null` reached the `String()` coercion
|
|
78
|
+
unguarded and compiled to the literal string `"null"`. Fifteen `sohl-thalorna`
|
|
79
|
+
notes shipped `"system": { "title": "null" }` that way.
|
|
80
|
+
|
|
81
|
+
**Nothing in any content tree relied on the fallback.** Across all three
|
|
82
|
+
consumers — 295 `affiliation` notes in `sohl-thalorna`, 28 in
|
|
83
|
+
`sohl-kethira-basic`, none in `sohl` — not one carries a non-empty top-level
|
|
84
|
+
`title`, and `content-build package compile` emits byte-identical
|
|
85
|
+
`build/packs-json` for all three. It is a major because the rule that decides a
|
|
86
|
+
consumer's compiled documents changed with no configuration to restore it, and
|
|
87
|
+
because the generated item-field reference moves (below).
|
|
88
|
+
|
|
89
|
+
**The field is still authorable**, at the two positions that describe the
|
|
90
|
+
document rather than the note: `sohl.system.title`, and the legacy in-block
|
|
91
|
+
`sohl.title` that most trees already write. A membership's title belongs on the
|
|
92
|
+
entry in a being's `sohl.items`, as its `system.title`. `data.title` is neither —
|
|
93
|
+
`title` is not a `data:` property any note type declares, so `content-build lint`
|
|
94
|
+
refuses it.
|
|
95
|
+
|
|
96
|
+
**Declaring the exemption:** a field in an `itemBuilders` `fields:` declaration
|
|
97
|
+
may now carry `topLevelMeans`, whose value is _what the note's top-level key of
|
|
98
|
+
that name means instead_. Declaring it removes the shared top-level position from
|
|
99
|
+
that field's resolution order, and the generated item-field reference prints the
|
|
100
|
+
reason beneath the type's table, so an author reading it learns that the
|
|
101
|
+
top-level key will not fill the field. A repository that commits that page should
|
|
102
|
+
regenerate it.
|
|
103
|
+
|
|
104
|
+
The value is the reason rather than a bare flag deliberately: a boolean would
|
|
105
|
+
record the decision and lose the case for it, and the next person adding a field
|
|
106
|
+
needs to know the question exists.
|
|
107
|
+
- a41b066: **A page states its address relative to the site root.** `site.base` is no
|
|
108
|
+
longer written into a page's Hugo `url:` front matter — only into the `href`s
|
|
109
|
+
this build renders and the base a link-manifest `path` is measured against:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
# emitted page
|
|
113
|
+
url: /doc-rulesintro/ # was: /sohl/doc-rulesintro/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`site.base` was two quantities wearing one name. It is correctly _where the
|
|
117
|
+
package is served_ — the prefix on every rendered `href`, and the base a
|
|
118
|
+
manifest `path` is stripped against — and `/<contentPackage>/` is the right
|
|
119
|
+
default for that. It was also written verbatim into each page's `url:`, and that
|
|
120
|
+
is a different quantity: Hugo resolves `url` against `baseURL`, whose path for
|
|
121
|
+
every consumer that exists **already is** the package base. So the prefix was
|
|
122
|
+
written twice, and every content page — plus the homepage — published one
|
|
123
|
+
package segment too deep:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
/sohl/doc-rulesintro/ 404 /sohl/sohl/doc-rulesintro/ 200
|
|
127
|
+
/thalorna/being-afzndhprnzr/ 404 /thalorna/thalorna/being-afzndhprnzr/ 200
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Not one address a link manifest advertised resolved: **0 of `sohl`'s 2,988
|
|
131
|
+
entries**, and 0 of `thalorna`'s 2,585.
|
|
132
|
+
|
|
133
|
+
**One value fed two readers that need opposite framings**, which is why no
|
|
134
|
+
setting could fix it from a consumer: `site.base: "/"` bought the addresses and
|
|
135
|
+
short-changed the hrefs, and the default did the reverse. They are now separate,
|
|
136
|
+
and each reader gets the form it needs:
|
|
137
|
+
|
|
138
|
+
| Reader | Gets | Because |
|
|
139
|
+
| --------------------------------------------------- | --------------------------- | ------------------------------------------------------------------- |
|
|
140
|
+
| A page's own `url:` front matter | `/<type>-<shortcode>/` | Hugo prefixes the site's `baseURL` path to it |
|
|
141
|
+
| The address index a `[[wikilink]]` resolves through | `<base><type>-<shortcode>/` | A browser resolves the rendered `href` against nothing |
|
|
142
|
+
| A link-manifest `path` | `<type>-<shortcode>/` | Measured against `site.base` and stripped; unchanged, byte for byte |
|
|
143
|
+
|
|
144
|
+
The homepage moves with them: `homepageFrontmatter` states `/homepage-root/`,
|
|
145
|
+
and no longer takes a `base` (nor does `writeHomepages`' fourth argument, which
|
|
146
|
+
existed only to supply it). `trees` pages and section landings never stated a
|
|
147
|
+
`url:` and are untouched — they take their address from their path.
|
|
148
|
+
|
|
149
|
+
**Take this release and drop your `site.base`.** Both publishing consumers set
|
|
150
|
+
`site.base: "/"` as a stopgap
|
|
151
|
+
(HeroicLands/Song-of-Heroic-Lands-FoundryVTT#1813, HeroicLands/sohl-thalorna#131),
|
|
152
|
+
which bought the correct addresses at the cost of same-package body links
|
|
153
|
+
rendering `/doc-x/` rather than `/sohl/doc-x/` — dead either way, so nothing
|
|
154
|
+
regressed. With this release the stopgap is no longer needed and no longer
|
|
155
|
+
harmless: keeping it leaves those hrefs short. Delete the `base:` line and the
|
|
156
|
+
default is right for both halves.
|
|
157
|
+
|
|
158
|
+
**Verified against pristine `git archive origin/main` extractions of all three
|
|
159
|
+
consumers**, in both configurations, through `content-build site` **and** Hugo
|
|
160
|
+
0.165:
|
|
161
|
+
|
|
162
|
+
| Consumer | `site.base` | Page `url:` | Rendered path | Manifest entries resolving |
|
|
163
|
+
| ---------- | ----------- | --------------------- | ------------------------------ | -------------------------- |
|
|
164
|
+
| `sohl` | unset | `/doc-rulesintro/` | `/sohl/doc-rulesintro/` | 2,988 of 2,988 (was 0) |
|
|
165
|
+
| `sohl` | `"/"` | `/doc-rulesintro/` | `/sohl/doc-rulesintro/` | 2,988 of 2,988 |
|
|
166
|
+
| `thalorna` | unset | `/being-afzndhprnzr/` | `/thalorna/being-afzndhprnzr/` | 2,585 of 2,585 (was 0) |
|
|
167
|
+
| `thalorna` | `"/"` | `/being-afzndhprnzr/` | `/thalorna/being-afzndhprnzr/` | 2,585 of 2,585 |
|
|
168
|
+
|
|
169
|
+
No `/sohl/sohl/` or `/thalorna/thalorna/` path exists in either built tree, the
|
|
170
|
+
section landings, the content mount and the `dev-docs` tree pages all still
|
|
171
|
+
resolve, and with the stopgap dropped a same-package body link renders
|
|
172
|
+
`<a href=/sohl/doc-skills/>` again. `lint` and `links` are identical line for
|
|
173
|
+
line in every combination — `sohl` green, `sohl-thalorna` exactly as red as its
|
|
174
|
+
own content gap leaves it (1,983 lint findings, 122 link findings).
|
|
175
|
+
|
|
176
|
+
**Exactly one line per page changes, and nothing else does.** Of `sohl`'s 1,671
|
|
177
|
+
emitted files, 1,606 differ — the 1,605 content pages and the homepage — each by
|
|
178
|
+
its `url:` alone; the 46 tree pages and 19 landings are byte-identical, and
|
|
179
|
+
`build/manifests/sohl.json` is byte-identical. `sohl-kethira-basic` publishes a
|
|
180
|
+
homepage and nothing else (`publish.site: homepage`) and declares no
|
|
181
|
+
`site.base`: its console output is identical, its one emitted page moves from
|
|
182
|
+
`/kethira/kethira/homepage-root/` to `/kethira/homepage-root/`, and its landing
|
|
183
|
+
at `/kethira/` is unaffected.
|
|
184
|
+
|
|
185
|
+
Closes #217
|
|
186
|
+
|
|
187
|
+
## 14.0.0
|
|
188
|
+
|
|
189
|
+
### Major Changes
|
|
190
|
+
|
|
191
|
+
- a0a113b: **`publish.address.landing` is deleted.** `prefix` is the whole address scheme:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
publish:
|
|
195
|
+
address:
|
|
196
|
+
prefix: kb/ # default: "" — the package root
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The key named which note addressed a whole section rather than a page within
|
|
200
|
+
one. #202 retired the second of its two rules and #204 retired the concept both
|
|
201
|
+
rules chose between — a section is a Hugo content directory the note format does
|
|
202
|
+
not carry, a page's address names no directory, and so no note lands one. What
|
|
203
|
+
survived was the key itself: resolved, refused-by-name for the retired value,
|
|
204
|
+
checked against a one-element vocabulary, frozen into the configuration, and
|
|
205
|
+
read by nobody. `LANDING_RULES` said so in its own doc comment — _"Inert since
|
|
206
|
+
#204."_
|
|
207
|
+
|
|
208
|
+
**A configuration still declaring it is refused, at the line it is written on.**
|
|
209
|
+
Not reported as an unrecognized option, which names a spelling to correct and
|
|
210
|
+
leaves the author to work out that the mechanism is gone:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
package-build.config.yaml:14:9: error: package-build config:
|
|
214
|
+
`publish.address.landing` is a retired option — delete it. It named which note
|
|
215
|
+
addressed a whole section rather than a page within one, and there are no
|
|
216
|
+
sections to address: a section is a Hugo content directory the note format does
|
|
217
|
+
not carry, so no note lands one and every page is addressed
|
|
218
|
+
`<type>-<shortcode>`. Nothing replaces it.
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Presence is the whole test, as it is for a retired frontmatter field: no value
|
|
222
|
+
makes declaring it right, so `readme` and the already-retired `collection` are
|
|
223
|
+
refused alike, by `RETIRED_ADDRESS_KEYS` — the configuration-side twin of
|
|
224
|
+
`engine/retired-fields.mjs`.
|
|
225
|
+
|
|
226
|
+
**Why the key outlived its mechanism by one release.** `content-config.mjs` has
|
|
227
|
+
no warning channel — every finding goes through `fail()`, which throws — so
|
|
228
|
+
while both publishing consumers still declared the then-true `landing: readme`
|
|
229
|
+
the only options were to break them over a correct statement or to accept the
|
|
230
|
+
key in silence, and silent acceptance is what this codebase refuses everywhere
|
|
231
|
+
else. So it took the three steps `package:` took (#56): retire the value, have
|
|
232
|
+
consumers drop the key, delete the key. No consumer declares it now.
|
|
233
|
+
|
|
234
|
+
**The plumbing goes with it.** `packageAddress` took an address scheme only to
|
|
235
|
+
validate the `landing` rule it then discarded — its own docstring already said
|
|
236
|
+
the `prefix` half never applied, because an address is `(type, shortcode)`, a
|
|
237
|
+
package-wide identity that takes no mount. It is now a function of the
|
|
238
|
+
frontmatter and nothing else, and `manifestContext` no longer carries a `scheme`
|
|
239
|
+
that nothing reads.
|
|
240
|
+
|
|
241
|
+
| Removed | Where |
|
|
242
|
+
| ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
|
|
243
|
+
| `LANDING_RULES`, `RETIRED_LANDING_RULES` | `content-config.mjs` |
|
|
244
|
+
| `DEFAULT_ADDRESS_SCHEME.landing`, `ADDRESS_KEYS`' second entry, `normalizePublish`'s resolve-and-check, `AddressSchemeInput.landing` | `content-config.mjs` |
|
|
245
|
+
| The `LANDING_RULES` re-export, `packageAddress`'s `{ scheme }` parameter and its landing check | `engine/content-address.mjs` |
|
|
246
|
+
| `manifestContext`'s `scheme`, and the argument `collectManifestEntries` passed on with it | `engine/manifest-emit.mjs` |
|
|
247
|
+
|
|
248
|
+
**Nothing a consumer emits moves.** Verified against pristine
|
|
249
|
+
`git archive origin/main` extractions of all three consumers, before and after:
|
|
250
|
+
`lint`, `links`, `manifest`, `package compile` and `site` produce **identical
|
|
251
|
+
console output, line for line**, and every emitted file is byte-identical —
|
|
252
|
+
31,197 files across the three trees, with only LevelDB's own timestamped `LOG`
|
|
253
|
+
differing. `sohl` stays green (2,988 manifest entries, 3,125 pack documents,
|
|
254
|
+
1,671 emitted pages); `sohl-thalorna` stays exactly as red as it was for its own
|
|
255
|
+
content gap (1,983 lint findings, 122 link findings); `sohl-kethira-basic` stays
|
|
256
|
+
green.
|
|
257
|
+
|
|
258
|
+
Closes #215
|
|
259
|
+
- 98ac362: **`subType: user-guide` is refused.** #206 renamed the `doc` subType
|
|
260
|
+
`user-guide` to `userguide` and held every `type` and `subType` to the address
|
|
261
|
+
charset, but shipped a **transitional acceptance** for the old spelling — a
|
|
262
|
+
warning naming the replacement rather than a refusal — because 43 `sohl` notes
|
|
263
|
+
authored it and no consumer can sweep ahead of the release that renames a value.
|
|
264
|
+
Every consumer tree has now swept: `sohl` **0**, `sohl-thalorna` **0**,
|
|
265
|
+
`sohl-kethira-basic` **0**, counted on a pristine extraction of each
|
|
266
|
+
`origin/main`. So the acceptance guards nothing, and this is the follow-up #207
|
|
267
|
+
named.
|
|
268
|
+
|
|
269
|
+
`RETIRED_SUBTYPES`, `retiredSubType()` and `retiredSubTypeMessage()` are gone
|
|
270
|
+
from `engine/note-vocabulary.mjs`, along with the retired-spelling branch that
|
|
271
|
+
ran ahead of the charset check in `checkSubType`. Nothing replaces them:
|
|
272
|
+
`user-guide` now falls through to the **charset** check and is refused as an
|
|
273
|
+
error, for the reason that always applied — it contains a hyphen. That is why
|
|
274
|
+
the acceptance could be deleted rather than promoted to an error: the permanent
|
|
275
|
+
rule already covers the case, so no retirement-specific code outlived the sweep.
|
|
276
|
+
|
|
277
|
+
**Breaking**, though the diff only removes code. A spelling that built at exit 0
|
|
278
|
+
one release ago now fails the build, and three exported symbols no longer exist.
|
|
279
|
+
A tree that has swept sees no change at all — which all three consumers have,
|
|
280
|
+
and each was verified unaffected.
|
|
281
|
+
|
|
282
|
+
**The subType charset diagnostic is reworded.** It justified the rule by "the
|
|
283
|
+
hyphen separates the segments of an address", true of a `subType` when #206
|
|
284
|
+
shipped — `sectionOf` returned a `doc`'s subType, so the value was a URL path
|
|
285
|
+
segment — and not true since #204 retired sections. The rule stands on its own
|
|
286
|
+
footing instead: a subType is a vocabulary term the whole toolchain keys on, one
|
|
287
|
+
closed set away from being an address segment again, and a charset holding for a
|
|
288
|
+
type, a shortcode and a `contentPackage` but not for a subType would be a rule
|
|
289
|
+
nobody could state in a sentence. `typeCharsetMessage` is untouched — a type
|
|
290
|
+
genuinely is the first segment of every address.
|
|
291
|
+
|
|
292
|
+
The same correction is applied to `assertVocabularyCharset`'s throw, which
|
|
293
|
+
carried the identical claim in a second place — _"A type and a subType are both
|
|
294
|
+
address segments"_ — where it would go unread until it fires, which is exactly
|
|
295
|
+
when it would be taken at face value. It now states the reason **per key**: the
|
|
296
|
+
address half for a type, the vocabulary-term half for a subType. The guard
|
|
297
|
+
itself is unchanged and stays where it is, running over `NOTE_VOCABULARY` as the
|
|
298
|
+
module loads.
|
|
299
|
+
|
|
300
|
+
Closes #210
|
|
301
|
+
|
|
302
|
+
### Minor Changes
|
|
303
|
+
|
|
304
|
+
- 45b5bd0: **A section can say what it lists.** `site.sections` / `site.readmeSections`
|
|
305
|
+
take two more keys, `listType` and `listSubType`, and both reach the generated
|
|
306
|
+
`_index.md`:
|
|
307
|
+
|
|
308
|
+
```yaml
|
|
309
|
+
sections:
|
|
310
|
+
weapongear: { title: Weapons, listType: weapongear }
|
|
311
|
+
user-guide: { title: User Guide, listType: doc, listSubType: userguide }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Since #204 a content page is written flat under the mount, so a declared
|
|
315
|
+
section's directory holds nothing but the landing this build writes for it and a
|
|
316
|
+
layout reading Hugo's `.Pages` finds no members. The membership survives in the
|
|
317
|
+
`site.sections` map and in nothing a theme can read — not on the page, not on
|
|
318
|
+
the landing, not in any URL — so every section landing served by a generic list
|
|
319
|
+
layout renders empty. `sohl` was unaffected only because its eleven catalog
|
|
320
|
+
layouts already query `site.RegularPages` by `Params.type`; a consumer rendering
|
|
321
|
+
through the shared theme has no layout of its own to edit. The landing now
|
|
322
|
+
states that query and the theme runs it
|
|
323
|
+
(HeroicLands/heroiclands-hugo-theme#50).
|
|
324
|
+
|
|
325
|
+
**Two keys of their own, not `type` / `subType`.** On an `_index.md`, `type` is
|
|
326
|
+
Hugo's own layout selector: verified against Hugo 0.165, a section landing
|
|
327
|
+
carrying `type: doc` renders through `layouts/doc/list.html` rather than the
|
|
328
|
+
default list template — behaviour this build already relies on deliberately, for
|
|
329
|
+
the mount's own `landing`. Spelling the content type there would silently change
|
|
330
|
+
which template serves the landing.
|
|
331
|
+
|
|
332
|
+
**Two keys added to the closed set, not an open passthrough.** `site.landing` is
|
|
333
|
+
passed through unvalidated because it is written once, for the mount, in one
|
|
334
|
+
landing template's own vocabulary; a section entry is written fourteen to twenty
|
|
335
|
+
times per build against a contract every package and every section shares.
|
|
336
|
+
Unbounded there, a mistyped `listTpye:` would publish into front matter, list
|
|
337
|
+
nothing, and report no error — which is the bug being fixed, moved one step
|
|
338
|
+
downstream where no build can see it. `normalizeSectionMeta` stays the one place
|
|
339
|
+
the vocabulary is bounded, and the writers still name no keys.
|
|
340
|
+
|
|
341
|
+
**Both values are checked, because both ways of writing an inert declaration are
|
|
342
|
+
silent.** They name a content type and subType, so each must be an address
|
|
343
|
+
segment (`^[A-Za-z0-9]+$`), and a `listSubType` with no `listType` is refused —
|
|
344
|
+
a subType tells pages apart only within a type, so alone it names no query. The
|
|
345
|
+
charset check is the trap this came from: a section is named for a URL the site
|
|
346
|
+
chose and need not match the address (`/sohl/kb/user-guide/` is the section,
|
|
347
|
+
`userguide` the subType, #207), and copying the section's name in would match no
|
|
348
|
+
page at exit 0. All three refusals are located at the offending key:
|
|
349
|
+
|
|
350
|
+
```text
|
|
351
|
+
package-build.config.yaml:504:90: error: package-build config:
|
|
352
|
+
`site.sections.user-guide.listSubType` is `user-guide`, which is not
|
|
353
|
+
alphanumeric. …
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Additive.** A section that declares neither key emits exactly the bytes it
|
|
357
|
+
did before. Verified on a pristine `origin/main` extraction of `sohl`, the only
|
|
358
|
+
consumer running `content-build site` with declared sections: 1670 emitted files
|
|
359
|
+
byte-identical, and with the keys declared on two of its nineteen sections
|
|
360
|
+
exactly those two `_index.md` files change.
|
|
361
|
+
|
|
362
|
+
Closes #212
|
|
363
|
+
|
|
3
364
|
## 13.0.0
|
|
4
365
|
|
|
5
366
|
### Major Changes
|