@heroiclands/package-build 6.0.0 → 6.1.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 +184 -0
- package/CONTENT.md +207 -3
- package/bin/content-build.mjs +168 -1
- package/content-config.mjs +29 -7
- package/engine/address-diff.mjs +290 -0
- package/engine/content-lint.mjs +23 -2
- package/engine/frontmatter-lint.mjs +22 -0
- package/engine/homepage.mjs +206 -2
- package/engine/site-build.mjs +74 -19
- package/package.json +1 -1
- package/types/engine/address-diff.d.mts +108 -0
- package/types/engine/content-lint.d.mts +4 -1
- package/types/engine/homepage.d.mts +117 -0
- package/types/engine/site-build.d.mts +28 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,189 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 6.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- dc424d7: **`content-build addresses diff` reports every published address a build has
|
|
8
|
+
stopped publishing, and tells a rename from a removal** (#66).
|
|
9
|
+
|
|
10
|
+
A package's `(type, shortcode)` addresses are a published interface — every
|
|
11
|
+
satellite declaring `itemCatalog: true` assembles its beings out of them — but
|
|
12
|
+
nothing compared one release's addresses against the next, so renaming a
|
|
13
|
+
shortcode cost nothing and produced no signal. The check that got made instead
|
|
14
|
+
was a repository-local grep, which cannot see the other repositories and reports
|
|
15
|
+
the reassuring answer. `sohl` renamed `weapongear:Tabri` to `weapongear:Taburi`
|
|
16
|
+
two days after the `v0.8.2` tag both satellites pin, stating that nothing
|
|
17
|
+
referenced the old value; five lookups across two repositories do, and they fail
|
|
18
|
+
the moment either pin moves.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gh release download v0.8.2 -p system.zip -D build/baseline
|
|
22
|
+
npx content-build addresses diff --from build/baseline/system.zip
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run against `sohl` today that reports 20 findings — 8 renames and 12
|
|
26
|
+
withdrawals — including the `Tabri` one, at the line of the note that made it:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
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
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**A rename is told from a removal by the document id, which is an identity match
|
|
33
|
+
rather than an inference.** A note authors its `_id` in frontmatter and it is not
|
|
34
|
+
derived from the shortcode, so it survives a rename. Where the id is published
|
|
35
|
+
under no address at all, the finding says only that — _withdrawn_, naming no
|
|
36
|
+
successor. A split, a deletion and a merge are indistinguishable at that point,
|
|
37
|
+
and a "did you mean" guessed from string similarity would send the reader to the
|
|
38
|
+
wrong fix.
|
|
39
|
+
|
|
40
|
+
Both findings are warnings and neither fails a build: retiring content is
|
|
41
|
+
legitimate, and so is renaming — the shortcode charset rule forces some. What a
|
|
42
|
+
rename must not do is happen in silence. `--strict` reports both as errors and
|
|
43
|
+
exits non-zero, for a release workflow that wants a gate.
|
|
44
|
+
|
|
45
|
+
Additive: nothing runs unless the command is invoked, and no existing behaviour
|
|
46
|
+
changed.
|
|
47
|
+
- f28f653: A section can now describe itself: `site.sections` and `site.readmeSections`
|
|
48
|
+
take a `description`, and it reaches the generated `_index.md` (#91).
|
|
49
|
+
|
|
50
|
+
A generated section landing is the only place a section can speak — a content
|
|
51
|
+
package authors no `_index.md` for `weapongear` or `affliction`, so the file the
|
|
52
|
+
theme reads is the one this build writes. Its vocabulary was two keys, and
|
|
53
|
+
`partials/hero-banner.html` has always rendered `description` as the hero
|
|
54
|
+
standfirst, so every generated landing rendered a heading with no standfirst and
|
|
55
|
+
could not be given one by any consumer, at any level.
|
|
56
|
+
|
|
57
|
+
**The pair was the defect, not a passthrough.** The vocabulary lived in two
|
|
58
|
+
places that had to be kept in step by hand: the schema that admits a key, and
|
|
59
|
+
the two writers that each transcribed `title` and `banner` by name. A key added
|
|
60
|
+
to the schema alone validated cleanly and then reached no page. So the writers
|
|
61
|
+
stop naming keys and emit what the section resolved to, `title` first, and the
|
|
62
|
+
schema is now the single place a section's vocabulary is decided.
|
|
63
|
+
|
|
64
|
+
**The bound stays.** An unrecognised key under a section is refused at config
|
|
65
|
+
load, by name, exactly as before — `site.sections.affliction.descrption is not a
|
|
66
|
+
recognized option (expected one of: title, banner, description)`. Passing
|
|
67
|
+
sections through unvalidated the way `site.landing` is was weighed and refused:
|
|
68
|
+
`landing` is written once, for the mount, in one landing template's vocabulary,
|
|
69
|
+
where a section entry is written fourteen to twenty times per build against a
|
|
70
|
+
contract every package shares. Unbounded, a mistyped `descrption:` would publish
|
|
71
|
+
into front matter, be read by nobody, and say nothing to anyone — which is the
|
|
72
|
+
failure this issue was filed about, one step downstream where no build can see
|
|
73
|
+
it.
|
|
74
|
+
|
|
75
|
+
Additive. `description` is optional and no consuming package declares one — none
|
|
76
|
+
could, since the loader has always refused it — so every generated landing is
|
|
77
|
+
byte-identical, verified across all six consuming packages.
|
|
78
|
+
- 43a9de4: Refuse `name`, `shortcode` and `id` on a `type: homepage` note (#53).
|
|
79
|
+
|
|
80
|
+
A note's URL derives from `name.full` and its identity from `(type, shortcode)`.
|
|
81
|
+
The homepage is the one page for which neither holds — it publishes at
|
|
82
|
+
`/<package>/`, fixed by the package id — so an author fluent in the conventions
|
|
83
|
+
writes them there expecting exactly what they do everywhere else, and gets none
|
|
84
|
+
of it. Until now `content-build lint` ignored all three: a homepage carrying
|
|
85
|
+
`shortcode`, `id` and `name.full` passed at exit 0.
|
|
86
|
+
|
|
87
|
+
**They were never inert, which is why ignoring them was the wrong answer.** A
|
|
88
|
+
`shortcode` puts the note in the address index and in the `dataview` link
|
|
89
|
+
universe, so `[[homepage-<shortcode>]]` resolves _green_ — to `homepage/<slug>/`,
|
|
90
|
+
an address derived from `name.full` and published by nothing, because a homepage
|
|
91
|
+
is written to `_index.md` at the package root. A build that reports a live link
|
|
92
|
+
to a 404 is worse than one that says nothing.
|
|
93
|
+
|
|
94
|
+
**The inflated address tally is the same defect, not a second one.** The same
|
|
95
|
+
note was counted as an address it does not publish, so the lint and the link
|
|
96
|
+
manifest disagreed about what the package ships: SoHL's tree reports
|
|
97
|
+
`1607 address(es) across 1607 note(s)` with a `shortcode` on its landing and
|
|
98
|
+
`1606 address(es) across 1607 note(s)` without one. The tally is only ever
|
|
99
|
+
printed on a clean run, so refusing the field is what makes the count honest —
|
|
100
|
+
`lintContentTree` needed no change, and got none.
|
|
101
|
+
|
|
102
|
+
**A named class, not an allow-list, and that boundary is the decision.** An
|
|
103
|
+
unknown top-level key is deliberately still accepted. A homepage's frontmatter is
|
|
104
|
+
emitted into the published page, so an unrecognised key is a Hugo or theme
|
|
105
|
+
parameter this build has never heard of and has no standing to reject, and a
|
|
106
|
+
closed list would make every new theme parameter wait on a package-build release.
|
|
107
|
+
What is refused is the class that makes a false claim about _where this page is_.
|
|
108
|
+
`aliases` is not in it either — it is already dropped from every emitted page, so
|
|
109
|
+
authoring one here is the same no-op it is anywhere else. This departs from the
|
|
110
|
+
issue's third acceptance criterion, deliberately: over-strictness here breaks
|
|
111
|
+
authoring on a page whose whole frontmatter is pass-through.
|
|
112
|
+
|
|
113
|
+
**Where it fires: `content-build lint` only.** This is a frontmatter-schema rule,
|
|
114
|
+
and `content-build site` runs none of them; wiring one type's field rule in there
|
|
115
|
+
would have the site build refuse `shortcode` on a homepage while accepting
|
|
116
|
+
`weight: heavy` on a weapon. The gap that leaves is `HarnMaster-3-FoundryVTT`,
|
|
117
|
+
which runs no `content-build lint` at all and so receives no frontmatter finding
|
|
118
|
+
of any kind — a missing script in that repository rather than a rule to duplicate
|
|
119
|
+
one at a time.
|
|
120
|
+
|
|
121
|
+
Each finding is located at the offending key and says what the field would have
|
|
122
|
+
decided:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
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
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Minor rather than major, measured rather than assumed.** A new hard error is
|
|
129
|
+
breaking only if it fails a previously-passing consumer. All six HeroicLands
|
|
130
|
+
content packages were linted at their default branch, before and after — `sohl`,
|
|
131
|
+
`hm3`, `thalorna`, `kethira`, `harnensemble` and `harnadventures` — and every one
|
|
132
|
+
produces byte-identical findings and the same exit code with the rule as without
|
|
133
|
+
it. None of the six authors any of the three fields on its homepage.
|
|
134
|
+
- d1166d0: Require exactly one `type: homepage` note per package (#52).
|
|
135
|
+
|
|
136
|
+
Every package is reachable at `/<package>/` and what a reader finds there is one
|
|
137
|
+
authored note in its content tree — but nothing required a package to have one,
|
|
138
|
+
so the failure mode of the whole arrangement was a package that builds green and
|
|
139
|
+
serves nothing at its own address.
|
|
140
|
+
|
|
141
|
+
**Zero and two are the same defect, at the same severity.** Neither is a warning,
|
|
142
|
+
because a build that proceeds past either publishes the wrong front page while
|
|
143
|
+
reporting success — which is exactly what a warning tolerates.
|
|
144
|
+
|
|
145
|
+
| Count | What ships |
|
|
146
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| **Zero** | Nothing at `/<package>/`, silently: the site build reports `wrote 0 homepage(s)` and exits 0. |
|
|
148
|
+
| **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. |
|
|
149
|
+
|
|
150
|
+
**It fires in `content-build lint` and in `content-build site`, because neither
|
|
151
|
+
one reaches every package.** `HarnMaster-3-FoundryVTT` runs `site` and no `lint`;
|
|
152
|
+
`sohl-thalorna` runs `lint` and its own site builder. A rule in one of them is a
|
|
153
|
+
rule two of the six packages do not have. Both call the same function, so this is
|
|
154
|
+
one rule with two call sites rather than two rules that can drift. In the site
|
|
155
|
+
build it runs _before the output tree is cleared_, so a failing gate cannot
|
|
156
|
+
destroy a good site to report a bad tree.
|
|
157
|
+
|
|
158
|
+
**It does not vary by `publish.site`.** That setting chooses whether the
|
|
159
|
+
_content_ surfaces are published; the homepage is the floor beneath both modes.
|
|
160
|
+
The lint call site reads no `site:` block at all, so it could not vary by mode
|
|
161
|
+
even if the rule wanted to.
|
|
162
|
+
|
|
163
|
+
**Zero has no file to name, and none is invented.** The locator is the content
|
|
164
|
+
root — a real path, and the directory the note has to be added to — with no line
|
|
165
|
+
and no column, as the diagnostic rules require. Two is reported once per note,
|
|
166
|
+
located at its own `type:` value and naming the other, because each note is a
|
|
167
|
+
place an author has to open and edit:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
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
|
|
171
|
+
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
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Minor rather than major, measured rather than assumed.** A new hard error is
|
|
175
|
+
breaking only if it fails a previously-passing consumer. All six HeroicLands
|
|
176
|
+
content packages were run at their default branch, before and after: `sohl`,
|
|
177
|
+
`hm3`, `thalorna`, `kethira`, `harnensemble` and `harnadventures` each carry
|
|
178
|
+
exactly one homepage note, and every one produces byte-identical findings and the
|
|
179
|
+
same exit code with the check as without it.
|
|
180
|
+
|
|
181
|
+
The issue's sequencing — ship it inert, flip it to an error later — was written
|
|
182
|
+
when the count was ~45 repositories and two homepages existed only in unmerged
|
|
183
|
+
pull requests. Both have since merged, the real count is six, and every one
|
|
184
|
+
passes today, so the warning window would protect nobody and the flip would be a
|
|
185
|
+
second pull request for no reason.
|
|
186
|
+
|
|
3
187
|
## 6.0.0
|
|
4
188
|
|
|
5
189
|
### Major Changes
|
package/CONTENT.md
CHANGED
|
@@ -474,6 +474,7 @@ npx content-build markdown [paths..] [--fix]
|
|
|
474
474
|
npx content-build manifest [root] [--out <dir>]
|
|
475
475
|
npx content-build site [--out <dir>]
|
|
476
476
|
npx content-build reachability <dir> [file] [--index <shortcode>]
|
|
477
|
+
npx content-build addresses diff --from <zip|dir> [--strict]
|
|
477
478
|
```
|
|
478
479
|
|
|
479
480
|
| Command | What it does |
|
|
@@ -487,6 +488,7 @@ npx content-build reachability <dir> [file] [--index <shortcode>]
|
|
|
487
488
|
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest). |
|
|
488
489
|
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website). |
|
|
489
490
|
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point. |
|
|
491
|
+
| `addresses` | Report every published item address this build has stopped publishing. See [Diffing published addresses](#diffing-published-addresses). |
|
|
490
492
|
|
|
491
493
|
Every path, pack name and root it needs comes from the consuming repository's
|
|
492
494
|
`package-build.config.yaml`, so the usual invocation takes no arguments beyond
|
|
@@ -505,7 +507,7 @@ npx content-build lint # the configured `paths.content`
|
|
|
505
507
|
npx content-build lint some/tree # or a tree named outright
|
|
506
508
|
```
|
|
507
509
|
|
|
508
|
-
Checks the
|
|
510
|
+
Checks the three rules every note's **identity** is authored against, and reports
|
|
509
511
|
each finding in the located form below:
|
|
510
512
|
|
|
511
513
|
- **Shape** — a `shortcode` is strictly ASCII-alphanumeric. It is the identity
|
|
@@ -514,6 +516,8 @@ each finding in the located form below:
|
|
|
514
516
|
- **Uniqueness** — `(type, shortcode)` names one note. A document is addressed
|
|
515
517
|
across _every_ pack of its document type, so routing two same-address notes to
|
|
516
518
|
different packs with `pack:` does not separate them.
|
|
519
|
+
- **The package's own address** — exactly one note claims `/<package>/`. See
|
|
520
|
+
[Exactly one homepage](#exactly-one-homepage) below.
|
|
517
521
|
|
|
518
522
|
It compiles nothing, opens no LevelDB and needs no Foundry manifest, so it runs
|
|
519
523
|
in about a second and can gate a commit. An empty or untyped tree **fails**
|
|
@@ -527,6 +531,48 @@ addressed by the package rather than by a slug — so a package in
|
|
|
527
531
|
permanently unkeyed. That tree passes; a tree holding no notes at all still
|
|
528
532
|
fails.
|
|
529
533
|
|
|
534
|
+
### Exactly one homepage
|
|
535
|
+
|
|
536
|
+
A content tree declares **exactly one** `type: homepage` note (#52). Zero is an
|
|
537
|
+
error and two is an error, at the same severity, because they are one defect: a
|
|
538
|
+
package whose front page is not the page a person chose.
|
|
539
|
+
|
|
540
|
+
- _Zero_ and the package serves nothing at `/<package>/` — the failure the
|
|
541
|
+
authored homepage exists to prevent, and a silent one: the site build reports
|
|
542
|
+
`wrote 0 homepage(s)` and exits 0.
|
|
543
|
+
- _Two_ and it serves a page nobody chose. Every homepage is written to the same
|
|
544
|
+
`_index.md`, so the second overwrites the first and the front page is decided
|
|
545
|
+
by the order the walk reached the files in — by _filename_, on a type whose
|
|
546
|
+
whole point is that it is routed by frontmatter. There is no "first wins"
|
|
547
|
+
convention to fall back on, so nothing can pick the right one.
|
|
548
|
+
|
|
549
|
+
Neither has a safe default, so neither is a warning: a build that proceeded past
|
|
550
|
+
either would publish the wrong front page while reporting success, which is
|
|
551
|
+
exactly what a warning tolerates.
|
|
552
|
+
|
|
553
|
+
**Where it fires: `lint` _and_ `site`.** No single command reaches every
|
|
554
|
+
package — `HarnMaster-3-FoundryVTT` runs `content-build site` and no
|
|
555
|
+
`content-build lint`; `sohl-thalorna` runs `content-build lint` and its own site
|
|
556
|
+
builder — so a rule in one of them is a rule two of the six packages do not
|
|
557
|
+
have. Both call the same function, so there is one rule and two call sites
|
|
558
|
+
rather than two rules. In the site build it runs **before the output tree is
|
|
559
|
+
cleared**, so a failing gate cannot destroy a good site to report a bad tree.
|
|
560
|
+
|
|
561
|
+
**It does not vary by `publish.site`.** That setting chooses whether the
|
|
562
|
+
_content_ surfaces are published; the homepage is the floor beneath both modes.
|
|
563
|
+
The lint call site reads no `site:` block at all, and so could not vary by mode
|
|
564
|
+
even if the rule wanted to.
|
|
565
|
+
|
|
566
|
+
Zero has no file to name, so the locator is the **content root** — a real path,
|
|
567
|
+
and the directory the note has to be added to. No line or column is invented for
|
|
568
|
+
it. Two is reported once per offending note, located at its own `type:` value and
|
|
569
|
+
naming the other, because each note is a place an author has to open and edit:
|
|
570
|
+
|
|
571
|
+
```text
|
|
572
|
+
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
|
|
573
|
+
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
|
|
574
|
+
```
|
|
575
|
+
|
|
530
576
|
### Frontmatter, against the schema its type declares
|
|
531
577
|
|
|
532
578
|
The same command also checks that each note's `sohl:` block is what its **type**
|
|
@@ -570,6 +616,55 @@ does not exist. Removing it was verified output-neutral first: across 1,735
|
|
|
570
616
|
stripped notes, `package compile` produced byte-identical `build/packs-json` and
|
|
571
617
|
the site build byte-identical `site/content`.
|
|
572
618
|
|
|
619
|
+
### The homepage carries no address of its own
|
|
620
|
+
|
|
621
|
+
A note's URL derives from `name.full` and its identity from
|
|
622
|
+
`(type, shortcode)`. The homepage is the one page for which neither holds: it
|
|
623
|
+
publishes at `/<package>/`, fixed by the package id. So `content-build lint`
|
|
624
|
+
**refuses** `name`, `shortcode` and `id` on a `type: homepage` note (#53) rather
|
|
625
|
+
than ignoring them — an author fluent in the conventions writes them here
|
|
626
|
+
expecting exactly what they do everywhere else, and gets none of it.
|
|
627
|
+
|
|
628
|
+
They were never inert, which is why ignoring them was the wrong answer. A
|
|
629
|
+
`shortcode` puts the note in the address index and in the `dataview` link
|
|
630
|
+
universe, so `[[homepage-<shortcode>]]` resolves _green_ — to `homepage/<slug>/`,
|
|
631
|
+
an address derived from `name.full` that the site build never writes, because a
|
|
632
|
+
homepage goes to `_index.md` at the package root. A build reporting a live link
|
|
633
|
+
to a 404 is worse than one saying nothing. It also inflates this command's own
|
|
634
|
+
address tally, so the lint and the link manifest disagree about what the package
|
|
635
|
+
publishes: SoHL's tree reports `1607 address(es) across 1607 note(s)` with a
|
|
636
|
+
`shortcode` on its landing and `1606 address(es) across 1607 note(s)` without
|
|
637
|
+
one. That is one defect, not two — the tally is only ever printed on a clean run,
|
|
638
|
+
so refusing the field is what makes the count honest.
|
|
639
|
+
|
|
640
|
+
Each finding is located at the offending key and says what the field would have
|
|
641
|
+
decided, not merely that it does not belong:
|
|
642
|
+
|
|
643
|
+
```text
|
|
644
|
+
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
|
|
645
|
+
assets/content/homepage.md:28:1: error: `name` decides nothing on a `type: homepage` note: a page's slug derives from `name.full`, and a homepage's destination is fixed — it is written to `_index.md` at the package's own address, `/<package>/`. Write `title:` for what the page is called, and delete `name`
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**A named class, not an allow-list.** The documented envelope is `type` plus an
|
|
649
|
+
optional `title`, with `landing`, `description` and `banner` legitimate beside
|
|
650
|
+
them — but an unknown top-level key is **not** refused, and that boundary is the
|
|
651
|
+
decision rather than an omission. A homepage's frontmatter is emitted into the
|
|
652
|
+
published page, so an unrecognised key is a Hugo or theme parameter this build
|
|
653
|
+
has never heard of and has no standing to reject; a closed list would make every
|
|
654
|
+
new theme parameter wait on a package-build release. What is refused is the
|
|
655
|
+
specific class that makes a false claim about _where this page is_. `aliases` is
|
|
656
|
+
not in that class either: it is already dropped from every emitted page, so
|
|
657
|
+
authoring one here is the same no-op it is anywhere else.
|
|
658
|
+
|
|
659
|
+
**Where it fires: `content-build lint` only.** Unlike a rule about the shape of
|
|
660
|
+
the _tree_, which the site build has its own reason to gate on, this is a
|
|
661
|
+
_frontmatter-schema_ rule and `content-build site` runs none of them — wiring in one type's field
|
|
662
|
+
rule would have the site build refuse `shortcode` on a homepage while accepting
|
|
663
|
+
`weight: heavy` on a weapon. The gap that leaves is `HarnMaster-3-FoundryVTT`,
|
|
664
|
+
which runs no `content-build lint` at all and so receives no frontmatter finding
|
|
665
|
+
of any kind; that is a missing script in that repository, not a rule to duplicate
|
|
666
|
+
one at a time.
|
|
667
|
+
|
|
573
668
|
### The homepage's own links
|
|
574
669
|
|
|
575
670
|
The homepage is the page a reader arrives at, and until #54 it was the one page
|
|
@@ -789,6 +884,10 @@ title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.t
|
|
|
789
884
|
What the module is, which system it needs, how to install it.
|
|
790
885
|
```
|
|
791
886
|
|
|
887
|
+
A package declares **exactly one** of these, and both `content-build lint` and
|
|
888
|
+
`content-build site` require it — see
|
|
889
|
+
[Exactly one homepage](#exactly-one-homepage).
|
|
890
|
+
|
|
792
891
|
That is the whole envelope. A homepage **compiles into no compendium
|
|
793
892
|
document**, appears in no pack and in no link manifest, and is addressed by the
|
|
794
893
|
_package_ rather than by its own name — so `name.full`, `shortcode` and `id`
|
|
@@ -853,7 +952,10 @@ site:
|
|
|
853
952
|
trees:
|
|
854
953
|
- { from: kb/dev-docs, section: dev-docs }
|
|
855
954
|
sections:
|
|
856
|
-
being:
|
|
955
|
+
being:
|
|
956
|
+
title: Beings
|
|
957
|
+
banner: banners/creature.webp
|
|
958
|
+
description: Folk, animals and the things that walk the world.
|
|
857
959
|
readmeSections:
|
|
858
960
|
dev-docs: { title: Developer Documentation, banner: banners/dev-docs.webp }
|
|
859
961
|
```
|
|
@@ -863,7 +965,7 @@ site:
|
|
|
863
965
|
| `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
|
|
864
966
|
| `base` | Where the package is served. Defaults to `/<contentPackage>/`. |
|
|
865
967
|
| `packages` | Which content packages this site renders. Defaults to its own. |
|
|
866
|
-
| `sections` |
|
|
968
|
+
| `sections` | What each section says about itself on its landing — see below. |
|
|
867
969
|
| `readmeSections` | The same, for a section whose landing comes from a `README`. |
|
|
868
970
|
| `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
|
|
869
971
|
| `backfillSections` | Write a bare `_index.md` for any other section directly under the mount. |
|
|
@@ -871,6 +973,42 @@ site:
|
|
|
871
973
|
| `pass` | A named bundle of this repository's own body rewrites. |
|
|
872
974
|
| `passOptions` | That bundle's options. |
|
|
873
975
|
|
|
976
|
+
### What a section may declare
|
|
977
|
+
|
|
978
|
+
A generated section landing is the **only** place a section can describe itself.
|
|
979
|
+
A content package authors no `_index.md` for `weapongear` or `affliction`, so
|
|
980
|
+
the file the theme reads is the one this build writes from `sections` — and
|
|
981
|
+
whatever that entry may carry is the whole of what the section can say.
|
|
982
|
+
|
|
983
|
+
| Key | Required | What it does |
|
|
984
|
+
| ------------- | -------- | ------------------------------------------------------------------------- |
|
|
985
|
+
| `title` | yes | The landing's heading, so it matches the card that links to it. |
|
|
986
|
+
| `banner` | no | The hero image, resolved as a CDN asset like any other `banner:`. |
|
|
987
|
+
| `description` | no | The hero standfirst under the heading, and the blurb a landing card uses. |
|
|
988
|
+
|
|
989
|
+
`readmeSections` takes the same three, for a section whose landing comes from a
|
|
990
|
+
`README` rather than from nothing. What the section declares wins over what the
|
|
991
|
+
`README` happens to carry — the landing has to match the card that links to it.
|
|
992
|
+
|
|
993
|
+
**The vocabulary is closed, and a key outside it is refused by name:**
|
|
994
|
+
|
|
995
|
+
```text
|
|
996
|
+
package-build config: `site.sections.affliction.descrption` is not a
|
|
997
|
+
recognized option (expected one of: title, banner, description).
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
That refusal is the point. `landing` is passed through unvalidated because it is
|
|
1001
|
+
written once, for the mount, in one landing template's own vocabulary. A section
|
|
1002
|
+
entry is written fourteen to twenty times per build against a contract every
|
|
1003
|
+
package and every section shares, so an unbounded one would let a mistyped
|
|
1004
|
+
`descrption:` publish into front matter, be read by nobody, and say nothing to
|
|
1005
|
+
anyone. Refusing it costs one line here when the vocabulary genuinely grows, and
|
|
1006
|
+
buys a build that cannot quietly emit a key no theme reads.
|
|
1007
|
+
|
|
1008
|
+
The **writers** name no keys: a section's `_index.md` is whatever the entry
|
|
1009
|
+
resolved to, `title` first. So extending the vocabulary is a change to the
|
|
1010
|
+
schema alone, and the two can no longer drift apart.
|
|
1011
|
+
|
|
874
1012
|
### Why `out` is required
|
|
875
1013
|
|
|
876
1014
|
The output tree is a build artifact and is **deleted on every run**, so that a
|
|
@@ -931,6 +1069,72 @@ None of them exits the process from inside the library; the command decides. Tha
|
|
|
931
1069
|
is what makes them testable, which the consumer scripts' inline `process.exit`
|
|
932
1070
|
calls were not.
|
|
933
1071
|
|
|
1072
|
+
## Diffing published addresses
|
|
1073
|
+
|
|
1074
|
+
```bash
|
|
1075
|
+
gh release download v0.8.2 -p system.zip -D build/baseline
|
|
1076
|
+
npx content-build addresses diff --from build/baseline/system.zip
|
|
1077
|
+
npx content-build addresses diff --from build/baseline/system.zip --strict
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
A package's `(type, shortcode)` addresses are a **published interface**. Every
|
|
1081
|
+
satellite declaring `itemCatalog: true` assembles its beings out of them —
|
|
1082
|
+
`attribute:str`, `skill:awar`, `weapongear:Tabri` — resolving each against the
|
|
1083
|
+
Item packs of the release its `compatibility.verified` pins. Renaming a
|
|
1084
|
+
shortcode is therefore a breaking change to something other repositories
|
|
1085
|
+
consume, and it used to cost nothing and produce no signal: the check that got
|
|
1086
|
+
made was a repository-local grep, which cannot see the other repositories and
|
|
1087
|
+
reports the reassuring answer.
|
|
1088
|
+
|
|
1089
|
+
`sohl` renamed one weapon's shortcode from `Tabri` to `Taburi` two days after
|
|
1090
|
+
the `v0.8.2` tag, on the stated ground that "nothing referenced the old value".
|
|
1091
|
+
True of that repository. Both satellites pin `v0.8.2` and address
|
|
1092
|
+
`weapongear:Tabri` on their copy of the same character — five lookups that
|
|
1093
|
+
resolve today and fail the moment either pin moves, reporting a missing item.
|
|
1094
|
+
|
|
1095
|
+
**A rename is told from a removal by the document id, and that is an identity
|
|
1096
|
+
match rather than an inference.** A note authors its `_id` in frontmatter; it is
|
|
1097
|
+
not derived from the shortcode, so it survives a rename. An address that
|
|
1098
|
+
disappeared while its document is still published elsewhere _is_ a rename:
|
|
1099
|
+
|
|
1100
|
+
```text
|
|
1101
|
+
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
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
Where the id is published under no address at all, that is all it says —
|
|
1105
|
+
**withdrawn**, with no successor named. A split, a deletion and a merge are
|
|
1106
|
+
indistinguishable at that point, and a "did you mean" guessed from string
|
|
1107
|
+
similarity would be worse than silence, because a wrong one sends the reader to
|
|
1108
|
+
the wrong fix.
|
|
1109
|
+
|
|
1110
|
+
| Finding | What it means | Severity |
|
|
1111
|
+
| ----------- | -------------------------------------------------------- | --------------------------------------- |
|
|
1112
|
+
| `renamed` | Address gone; the same document publishes under another. | `warning` — legitimate, but not silent |
|
|
1113
|
+
| `withdrawn` | Address gone; its document publishes under none. | `warning` — retiring content is allowed |
|
|
1114
|
+
|
|
1115
|
+
Neither fails a build. Retiring content is legitimate, and so is renaming — the
|
|
1116
|
+
shortcode charset rule forces some. What a rename must not do is happen without
|
|
1117
|
+
anyone noticing. `--strict` reports both as errors and exits non-zero, for a
|
|
1118
|
+
release workflow that wants a gate.
|
|
1119
|
+
|
|
1120
|
+
**A finding is placed against the note, not the pack it was read from.** The
|
|
1121
|
+
address space is read from compiled output because that is what actually ships;
|
|
1122
|
+
the content tree is read only to find the note carrying the id, so the reader
|
|
1123
|
+
lands on the `shortcode:` line they just edited. A withdrawal has no such note,
|
|
1124
|
+
so it degrades to the baseline document — and where neither is readable the
|
|
1125
|
+
position is dropped rather than guessed.
|
|
1126
|
+
|
|
1127
|
+
**The baseline is named, never derived, and never downloaded.** `--from` takes
|
|
1128
|
+
the artifact for the same reason `deps fetch --from` does: a command that
|
|
1129
|
+
reaches the network on its own is not reproducible and fails strangely offline.
|
|
1130
|
+
A baseline that yields **no** addressable item is refused rather than read as
|
|
1131
|
+
"nothing changed" — it would report a clean result for every possible input,
|
|
1132
|
+
which is the one failure a check like this can never catch.
|
|
1133
|
+
|
|
1134
|
+
Item packs only, because that is the address space consumers resolve against: a
|
|
1135
|
+
being's embedded items are the only cross-package resolution by
|
|
1136
|
+
`(type, shortcode)`.
|
|
1137
|
+
|
|
934
1138
|
## Diagnostics
|
|
935
1139
|
|
|
936
1140
|
Every warning or error a build reports **about a content note** is emitted in the
|