@heroiclands/package-build 13.0.0 → 14.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 +177 -0
- package/CONTENT.md +65 -24
- package/MIGRATING.md +11 -5
- package/content-config.mjs +99 -61
- package/docs/content-format.md +7 -8
- package/engine/content-address.mjs +11 -19
- package/engine/frontmatter-lint.mjs +14 -32
- package/engine/manifest-emit.mjs +4 -7
- package/engine/note-vocabulary.mjs +34 -76
- package/package.json +1 -1
- package/types/content-config.d.mts +25 -35
- package/types/engine/content-address.d.mts +7 -14
- package/types/engine/manifest-emit.d.mts +3 -9
- package/types/engine/note-vocabulary.d.mts +17 -47
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,182 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 14.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- a0a113b: **`publish.address.landing` is deleted.** `prefix` is the whole address scheme:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
publish:
|
|
11
|
+
address:
|
|
12
|
+
prefix: kb/ # default: "" — the package root
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The key named which note addressed a whole section rather than a page within
|
|
16
|
+
one. #202 retired the second of its two rules and #204 retired the concept both
|
|
17
|
+
rules chose between — a section is a Hugo content directory the note format does
|
|
18
|
+
not carry, a page's address names no directory, and so no note lands one. What
|
|
19
|
+
survived was the key itself: resolved, refused-by-name for the retired value,
|
|
20
|
+
checked against a one-element vocabulary, frozen into the configuration, and
|
|
21
|
+
read by nobody. `LANDING_RULES` said so in its own doc comment — _"Inert since
|
|
22
|
+
#204."_
|
|
23
|
+
|
|
24
|
+
**A configuration still declaring it is refused, at the line it is written on.**
|
|
25
|
+
Not reported as an unrecognized option, which names a spelling to correct and
|
|
26
|
+
leaves the author to work out that the mechanism is gone:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
package-build.config.yaml:14:9: error: package-build config:
|
|
30
|
+
`publish.address.landing` is a retired option — delete it. It named which note
|
|
31
|
+
addressed a whole section rather than a page within one, and there are no
|
|
32
|
+
sections to address: a section is a Hugo content directory the note format does
|
|
33
|
+
not carry, so no note lands one and every page is addressed
|
|
34
|
+
`<type>-<shortcode>`. Nothing replaces it.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Presence is the whole test, as it is for a retired frontmatter field: no value
|
|
38
|
+
makes declaring it right, so `readme` and the already-retired `collection` are
|
|
39
|
+
refused alike, by `RETIRED_ADDRESS_KEYS` — the configuration-side twin of
|
|
40
|
+
`engine/retired-fields.mjs`.
|
|
41
|
+
|
|
42
|
+
**Why the key outlived its mechanism by one release.** `content-config.mjs` has
|
|
43
|
+
no warning channel — every finding goes through `fail()`, which throws — so
|
|
44
|
+
while both publishing consumers still declared the then-true `landing: readme`
|
|
45
|
+
the only options were to break them over a correct statement or to accept the
|
|
46
|
+
key in silence, and silent acceptance is what this codebase refuses everywhere
|
|
47
|
+
else. So it took the three steps `package:` took (#56): retire the value, have
|
|
48
|
+
consumers drop the key, delete the key. No consumer declares it now.
|
|
49
|
+
|
|
50
|
+
**The plumbing goes with it.** `packageAddress` took an address scheme only to
|
|
51
|
+
validate the `landing` rule it then discarded — its own docstring already said
|
|
52
|
+
the `prefix` half never applied, because an address is `(type, shortcode)`, a
|
|
53
|
+
package-wide identity that takes no mount. It is now a function of the
|
|
54
|
+
frontmatter and nothing else, and `manifestContext` no longer carries a `scheme`
|
|
55
|
+
that nothing reads.
|
|
56
|
+
|
|
57
|
+
| Removed | Where |
|
|
58
|
+
| ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------- |
|
|
59
|
+
| `LANDING_RULES`, `RETIRED_LANDING_RULES` | `content-config.mjs` |
|
|
60
|
+
| `DEFAULT_ADDRESS_SCHEME.landing`, `ADDRESS_KEYS`' second entry, `normalizePublish`'s resolve-and-check, `AddressSchemeInput.landing` | `content-config.mjs` |
|
|
61
|
+
| The `LANDING_RULES` re-export, `packageAddress`'s `{ scheme }` parameter and its landing check | `engine/content-address.mjs` |
|
|
62
|
+
| `manifestContext`'s `scheme`, and the argument `collectManifestEntries` passed on with it | `engine/manifest-emit.mjs` |
|
|
63
|
+
|
|
64
|
+
**Nothing a consumer emits moves.** Verified against pristine
|
|
65
|
+
`git archive origin/main` extractions of all three consumers, before and after:
|
|
66
|
+
`lint`, `links`, `manifest`, `package compile` and `site` produce **identical
|
|
67
|
+
console output, line for line**, and every emitted file is byte-identical —
|
|
68
|
+
31,197 files across the three trees, with only LevelDB's own timestamped `LOG`
|
|
69
|
+
differing. `sohl` stays green (2,988 manifest entries, 3,125 pack documents,
|
|
70
|
+
1,671 emitted pages); `sohl-thalorna` stays exactly as red as it was for its own
|
|
71
|
+
content gap (1,983 lint findings, 122 link findings); `sohl-kethira-basic` stays
|
|
72
|
+
green.
|
|
73
|
+
|
|
74
|
+
Closes #215
|
|
75
|
+
- 98ac362: **`subType: user-guide` is refused.** #206 renamed the `doc` subType
|
|
76
|
+
`user-guide` to `userguide` and held every `type` and `subType` to the address
|
|
77
|
+
charset, but shipped a **transitional acceptance** for the old spelling — a
|
|
78
|
+
warning naming the replacement rather than a refusal — because 43 `sohl` notes
|
|
79
|
+
authored it and no consumer can sweep ahead of the release that renames a value.
|
|
80
|
+
Every consumer tree has now swept: `sohl` **0**, `sohl-thalorna` **0**,
|
|
81
|
+
`sohl-kethira-basic` **0**, counted on a pristine extraction of each
|
|
82
|
+
`origin/main`. So the acceptance guards nothing, and this is the follow-up #207
|
|
83
|
+
named.
|
|
84
|
+
|
|
85
|
+
`RETIRED_SUBTYPES`, `retiredSubType()` and `retiredSubTypeMessage()` are gone
|
|
86
|
+
from `engine/note-vocabulary.mjs`, along with the retired-spelling branch that
|
|
87
|
+
ran ahead of the charset check in `checkSubType`. Nothing replaces them:
|
|
88
|
+
`user-guide` now falls through to the **charset** check and is refused as an
|
|
89
|
+
error, for the reason that always applied — it contains a hyphen. That is why
|
|
90
|
+
the acceptance could be deleted rather than promoted to an error: the permanent
|
|
91
|
+
rule already covers the case, so no retirement-specific code outlived the sweep.
|
|
92
|
+
|
|
93
|
+
**Breaking**, though the diff only removes code. A spelling that built at exit 0
|
|
94
|
+
one release ago now fails the build, and three exported symbols no longer exist.
|
|
95
|
+
A tree that has swept sees no change at all — which all three consumers have,
|
|
96
|
+
and each was verified unaffected.
|
|
97
|
+
|
|
98
|
+
**The subType charset diagnostic is reworded.** It justified the rule by "the
|
|
99
|
+
hyphen separates the segments of an address", true of a `subType` when #206
|
|
100
|
+
shipped — `sectionOf` returned a `doc`'s subType, so the value was a URL path
|
|
101
|
+
segment — and not true since #204 retired sections. The rule stands on its own
|
|
102
|
+
footing instead: a subType is a vocabulary term the whole toolchain keys on, one
|
|
103
|
+
closed set away from being an address segment again, and a charset holding for a
|
|
104
|
+
type, a shortcode and a `contentPackage` but not for a subType would be a rule
|
|
105
|
+
nobody could state in a sentence. `typeCharsetMessage` is untouched — a type
|
|
106
|
+
genuinely is the first segment of every address.
|
|
107
|
+
|
|
108
|
+
The same correction is applied to `assertVocabularyCharset`'s throw, which
|
|
109
|
+
carried the identical claim in a second place — _"A type and a subType are both
|
|
110
|
+
address segments"_ — where it would go unread until it fires, which is exactly
|
|
111
|
+
when it would be taken at face value. It now states the reason **per key**: the
|
|
112
|
+
address half for a type, the vocabulary-term half for a subType. The guard
|
|
113
|
+
itself is unchanged and stays where it is, running over `NOTE_VOCABULARY` as the
|
|
114
|
+
module loads.
|
|
115
|
+
|
|
116
|
+
Closes #210
|
|
117
|
+
|
|
118
|
+
### Minor Changes
|
|
119
|
+
|
|
120
|
+
- 45b5bd0: **A section can say what it lists.** `site.sections` / `site.readmeSections`
|
|
121
|
+
take two more keys, `listType` and `listSubType`, and both reach the generated
|
|
122
|
+
`_index.md`:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
sections:
|
|
126
|
+
weapongear: { title: Weapons, listType: weapongear }
|
|
127
|
+
user-guide: { title: User Guide, listType: doc, listSubType: userguide }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Since #204 a content page is written flat under the mount, so a declared
|
|
131
|
+
section's directory holds nothing but the landing this build writes for it and a
|
|
132
|
+
layout reading Hugo's `.Pages` finds no members. The membership survives in the
|
|
133
|
+
`site.sections` map and in nothing a theme can read — not on the page, not on
|
|
134
|
+
the landing, not in any URL — so every section landing served by a generic list
|
|
135
|
+
layout renders empty. `sohl` was unaffected only because its eleven catalog
|
|
136
|
+
layouts already query `site.RegularPages` by `Params.type`; a consumer rendering
|
|
137
|
+
through the shared theme has no layout of its own to edit. The landing now
|
|
138
|
+
states that query and the theme runs it
|
|
139
|
+
(HeroicLands/heroiclands-hugo-theme#50).
|
|
140
|
+
|
|
141
|
+
**Two keys of their own, not `type` / `subType`.** On an `_index.md`, `type` is
|
|
142
|
+
Hugo's own layout selector: verified against Hugo 0.165, a section landing
|
|
143
|
+
carrying `type: doc` renders through `layouts/doc/list.html` rather than the
|
|
144
|
+
default list template — behaviour this build already relies on deliberately, for
|
|
145
|
+
the mount's own `landing`. Spelling the content type there would silently change
|
|
146
|
+
which template serves the landing.
|
|
147
|
+
|
|
148
|
+
**Two keys added to the closed set, not an open passthrough.** `site.landing` is
|
|
149
|
+
passed through unvalidated because it is written once, for the mount, in one
|
|
150
|
+
landing template's own vocabulary; a section entry is written fourteen to twenty
|
|
151
|
+
times per build against a contract every package and every section shares.
|
|
152
|
+
Unbounded there, a mistyped `listTpye:` would publish into front matter, list
|
|
153
|
+
nothing, and report no error — which is the bug being fixed, moved one step
|
|
154
|
+
downstream where no build can see it. `normalizeSectionMeta` stays the one place
|
|
155
|
+
the vocabulary is bounded, and the writers still name no keys.
|
|
156
|
+
|
|
157
|
+
**Both values are checked, because both ways of writing an inert declaration are
|
|
158
|
+
silent.** They name a content type and subType, so each must be an address
|
|
159
|
+
segment (`^[A-Za-z0-9]+$`), and a `listSubType` with no `listType` is refused —
|
|
160
|
+
a subType tells pages apart only within a type, so alone it names no query. The
|
|
161
|
+
charset check is the trap this came from: a section is named for a URL the site
|
|
162
|
+
chose and need not match the address (`/sohl/kb/user-guide/` is the section,
|
|
163
|
+
`userguide` the subType, #207), and copying the section's name in would match no
|
|
164
|
+
page at exit 0. All three refusals are located at the offending key:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
package-build.config.yaml:504:90: error: package-build config:
|
|
168
|
+
`site.sections.user-guide.listSubType` is `user-guide`, which is not
|
|
169
|
+
alphanumeric. …
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Additive.** A section that declares neither key emits exactly the bytes it
|
|
173
|
+
did before. Verified on a pristine `origin/main` extraction of `sohl`, the only
|
|
174
|
+
consumer running `content-build site` with declared sections: 1670 emitted files
|
|
175
|
+
byte-identical, and with the keys declared on two of its nineteen sections
|
|
176
|
+
exactly those two `_index.md` files change.
|
|
177
|
+
|
|
178
|
+
Closes #212
|
|
179
|
+
|
|
3
180
|
## 13.0.0
|
|
4
181
|
|
|
5
182
|
### Major Changes
|
package/CONTENT.md
CHANGED
|
@@ -138,7 +138,6 @@ publish:
|
|
|
138
138
|
manifests: { publish: true, consume: true }
|
|
139
139
|
address:
|
|
140
140
|
prefix: kb/
|
|
141
|
-
landing: readme
|
|
142
141
|
|
|
143
142
|
# How this repository frames the website `content-build site` publishes.
|
|
144
143
|
# Framing only: addresses come from `publish.address` above.
|
|
@@ -806,15 +805,15 @@ rule is checked ahead of the closed-set check, which is what makes it reach a
|
|
|
806
805
|
type whose values are declared but not yet enumerated:
|
|
807
806
|
|
|
808
807
|
```text
|
|
809
|
-
assets/content/Beings/Folk.md:3:1: error: `subType` "common-folk" is not
|
|
808
|
+
assets/content/Beings/Folk.md:3:1: error: `subType` "common-folk" is not a well-formed subType — a subType is letters and digits only (^[A-Za-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. …
|
|
810
809
|
```
|
|
811
810
|
|
|
812
811
|
One declared value broke that rule: a `doc`'s `user-guide`, now **`userguide`**.
|
|
813
|
-
The old spelling
|
|
814
|
-
|
|
815
|
-
release before it had a chance to sweep
|
|
816
|
-
|
|
817
|
-
|
|
812
|
+
The old spelling was accepted for one transitional release, as a warning naming
|
|
813
|
+
the replacement, because an error would have redded every tree that took the
|
|
814
|
+
release before it had a chance to sweep. Every tree has swept, so the acceptance
|
|
815
|
+
is gone (#210): `user-guide` is refused by the charset check like any other
|
|
816
|
+
hyphenated value, and nothing retirement-specific was left to remove.
|
|
818
817
|
|
|
819
818
|
The vocabulary lives in `engine/note-vocabulary.mjs`, one entry per note type,
|
|
820
819
|
taken from the content-format specification. It is note-format knowledge rather
|
|
@@ -1247,9 +1246,8 @@ per section, and that is now the _only_ thing that makes one exist — see
|
|
|
1247
1246
|
|
|
1248
1247
|
### The address scheme
|
|
1249
1248
|
|
|
1250
|
-
Where the content tree mounts _inside the package_
|
|
1251
|
-
|
|
1252
|
-
both load-bearing. They are one setting, read by this command **and** by the page
|
|
1249
|
+
Where the content tree mounts _inside the package_ differs between repositories
|
|
1250
|
+
and is load-bearing. It is one setting, read by this command **and** by the page
|
|
1253
1251
|
emitter, so the address a manifest publishes is the address a page is emitted at
|
|
1254
1252
|
— stating it twice is how a manifest comes to assert a URL that resolves at build
|
|
1255
1253
|
time and 404s for the reader.
|
|
@@ -1260,7 +1258,6 @@ publish:
|
|
|
1260
1258
|
manifests: { publish: true, consume: true }
|
|
1261
1259
|
address:
|
|
1262
1260
|
prefix: kb/ # default: "" — the package root
|
|
1263
|
-
landing: readme # inert since #204; still accepted
|
|
1264
1261
|
```
|
|
1265
1262
|
|
|
1266
1263
|
- **`prefix`** — the content tree's mount within the package: the Hugo directory
|
|
@@ -1270,14 +1267,9 @@ publish:
|
|
|
1270
1267
|
content, so it has no prefix. It must end in a slash and must not begin with
|
|
1271
1268
|
one — where the _package_ is mounted is the consuming build's knowledge and is
|
|
1272
1269
|
never recorded here.
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
nothing; it is still accepted because both publishing consumers declare
|
|
1277
|
-
`landing: readme` and refusing a correct statement would break them. It is
|
|
1278
|
-
removed once no configuration writes it. The retired second rule, `collection`,
|
|
1279
|
-
is still refused by name (#202), along with the `collection` subtype and the
|
|
1280
|
-
top-level `section:` key.
|
|
1270
|
+
|
|
1271
|
+
`prefix` is the whole scheme. The `collection` subtype and the top-level
|
|
1272
|
+
`section:` key are refused by name (#202).
|
|
1281
1273
|
|
|
1282
1274
|
A note's `subType` is checked against the values its type declares, and only
|
|
1283
1275
|
those. It briefly had a second reading — a `README` landing's `subType` was the
|
|
@@ -1431,7 +1423,8 @@ Two consequences follow, and neither is optional:
|
|
|
1431
1423
|
`_index.md`, so a layout reading `.Pages` finds nothing. A layout that queries
|
|
1432
1424
|
`site.RegularPages` by `Params.type` is unaffected, and that is the shape a
|
|
1433
1425
|
content catalog wants anyway — it groups by what a page _is_, not by where its
|
|
1434
|
-
file happened to be written.
|
|
1426
|
+
file happened to be written. A section that wants that query run for it says
|
|
1427
|
+
so with `listType` — see below.
|
|
1435
1428
|
|
|
1436
1429
|
Whatever the entry may carry is the whole of what the section can say.
|
|
1437
1430
|
|
|
@@ -1440,18 +1433,63 @@ Whatever the entry may carry is the whole of what the section can say.
|
|
|
1440
1433
|
| `title` | yes | The landing's heading, so it matches the card that links to it. |
|
|
1441
1434
|
| `banner` | no | The hero image, resolved as a CDN asset like any other `banner:`. |
|
|
1442
1435
|
| `description` | no | The hero standfirst under the heading, and the blurb a landing card uses. |
|
|
1436
|
+
| `listType` | no | The content **type** whose pages this section lists. |
|
|
1437
|
+
| `listSubType` | no | Narrows that to one **subType**. Only with a `listType`. |
|
|
1443
1438
|
|
|
1444
|
-
`readmeSections` takes the same
|
|
1439
|
+
`readmeSections` takes the same keys, for a **`trees`** entry: those pages keep
|
|
1445
1440
|
their source layout below a named section, so the tree's own `README.md` is that
|
|
1446
1441
|
section's landing. What the section declares wins over what the `README` happens
|
|
1447
1442
|
to carry — the landing has to match the card that links to it. No content note
|
|
1448
1443
|
reaches this map any more.
|
|
1449
1444
|
|
|
1445
|
+
#### Saying what a section lists
|
|
1446
|
+
|
|
1447
|
+
A generic list layout has nothing to render, because the membership a section
|
|
1448
|
+
landing used to get free from Hugo's page tree no longer exists anywhere the
|
|
1449
|
+
theme can read: not on the page, not on the landing, not in any URL — only here.
|
|
1450
|
+
So a section states its own query, and a layout substitutes it when `.Pages` is
|
|
1451
|
+
empty:
|
|
1452
|
+
|
|
1453
|
+
```yaml
|
|
1454
|
+
sections:
|
|
1455
|
+
being: { title: Beings, listType: being }
|
|
1456
|
+
rules: { title: Rules, listType: doc, listSubType: rules }
|
|
1457
|
+
user-guide: { title: User Guide, listType: doc, listSubType: userguide }
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
emits, for the last of those, `user-guide/_index.md`:
|
|
1461
|
+
|
|
1462
|
+
```yaml
|
|
1463
|
+
---
|
|
1464
|
+
title: User Guide
|
|
1465
|
+
listType: doc
|
|
1466
|
+
listSubType: userguide
|
|
1467
|
+
---
|
|
1468
|
+
```
|
|
1469
|
+
|
|
1470
|
+
Three things about the spelling, each of them load-bearing:
|
|
1471
|
+
|
|
1472
|
+
- **Not `type`.** On an `_index.md` that is Hugo's own layout selector — a
|
|
1473
|
+
landing carrying `type: doc` is rendered by `layouts/doc/list.html` rather
|
|
1474
|
+
than the default list template — so writing the content type there would
|
|
1475
|
+
silently change which template serves the landing. This build already relies
|
|
1476
|
+
on that behaviour for the mount's own `landing`.
|
|
1477
|
+
- **Not inferred from the section's name.** A section is named for a URL the
|
|
1478
|
+
site chose; a type and a subType are addresses. They need not agree, and on
|
|
1479
|
+
`sohl` they do not: the section is `user-guide`, because that is a published
|
|
1480
|
+
URL, while the subType is `userguide` because an address segment is
|
|
1481
|
+
alphanumeric (#207). Both values are checked against that charset here, so
|
|
1482
|
+
copying the section's name in is refused rather than quietly matching nothing.
|
|
1483
|
+
- **`listSubType` needs a `listType`.** A subType only tells pages apart within
|
|
1484
|
+
a type — `rules`, `userguide` and `reference` are all `doc` — so alone it
|
|
1485
|
+
names no query.
|
|
1486
|
+
|
|
1450
1487
|
**The vocabulary is closed, and a key outside it is refused by name:**
|
|
1451
1488
|
|
|
1452
1489
|
```text
|
|
1453
1490
|
package-build config: `site.sections.affliction.descrption` is not a
|
|
1454
|
-
recognized option (expected one of: title, banner, description
|
|
1491
|
+
recognized option (expected one of: title, banner, description, listType,
|
|
1492
|
+
listSubType).
|
|
1455
1493
|
```
|
|
1456
1494
|
|
|
1457
1495
|
That refusal is the point. `landing` is passed through unvalidated because it is
|
|
@@ -1460,7 +1498,10 @@ entry is written fourteen to twenty times per build against a contract every
|
|
|
1460
1498
|
package and every section shares, so an unbounded one would let a mistyped
|
|
1461
1499
|
`descrption:` publish into front matter, be read by nobody, and say nothing to
|
|
1462
1500
|
anyone. Refusing it costs one line here when the vocabulary genuinely grows, and
|
|
1463
|
-
buys a build that cannot quietly emit a key no theme reads.
|
|
1501
|
+
buys a build that cannot quietly emit a key no theme reads. `listType` and
|
|
1502
|
+
`listSubType` are that growth: two named keys, checked, rather than an open
|
|
1503
|
+
passthrough in which `listTpye:` would publish and no landing would list
|
|
1504
|
+
anything.
|
|
1464
1505
|
|
|
1465
1506
|
The **writers** name no keys: a section's `_index.md` is whatever the entry
|
|
1466
1507
|
resolved to, `title` first. So extending the vocabulary is a change to the
|
|
@@ -1624,7 +1665,7 @@ path now rides on the error, and the loader that read the file resolves it
|
|
|
1624
1665
|
against the YAML, so all of them come out located:
|
|
1625
1666
|
|
|
1626
1667
|
```text
|
|
1627
|
-
package-build.config.yaml:382:64: error: package-build config: `site.sections.being.descrption` is not a recognized option (expected one of: title, banner).
|
|
1668
|
+
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, listType, listSubType).
|
|
1628
1669
|
```
|
|
1629
1670
|
|
|
1630
1671
|
The same two rules apply. A key the file never declares — a required one that is
|
package/MIGRATING.md
CHANGED
|
@@ -49,12 +49,18 @@ That is the shape a content catalog wants regardless — it groups by what a pag
|
|
|
49
49
|
_is_ rather than by where its file happened to be written — and it is what
|
|
50
50
|
`sohl`'s catalog layouts already do.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
A site rendering through a **shared theme** has no layout of its own to edit. As
|
|
53
|
+
of 13.1.0 the section declares its query instead, and the theme runs it:
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
```yaml
|
|
56
|
+
sections:
|
|
57
|
+
weapongear: { title: Weapons, listType: weapongear }
|
|
58
|
+
user-guide: { title: User Guide, listType: doc, listSubType: userguide }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`listType` is the content type, not the section's name — the two need not agree,
|
|
62
|
+
and `listSubType` narrows a type whose genres share it. See
|
|
63
|
+
[What a section may declare](CONTENT.md#what-a-section-may-declare).
|
|
58
64
|
|
|
59
65
|
## What did not change
|
|
60
66
|
|
package/content-config.mjs
CHANGED
|
@@ -68,7 +68,7 @@ import path from "node:path";
|
|
|
68
68
|
|
|
69
69
|
// Leaves with no local imports of their own, so naming them here cannot close
|
|
70
70
|
// a cycle around a consumer's config file (see `engine/pack-config.mjs`).
|
|
71
|
-
import { isAddressSegment } from "./engine/address-charset.mjs";
|
|
71
|
+
import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./engine/address-charset.mjs";
|
|
72
72
|
import { MAP_TYPES, PACK_BY_TYPE } from "./engine/ids.mjs";
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -116,44 +116,37 @@ export const PACK_DOCUMENT_TYPES = /** @type {const} */ ([
|
|
|
116
116
|
]);
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* A
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* A retired *value* is refused the way a retired *field* is (see
|
|
142
|
-
* `engine/retired-fields.mjs`): left merely unrecognized it would be reported
|
|
143
|
-
* as a bad value, which names something to correct and leaves the author to
|
|
144
|
-
* work out for themselves that the mechanism is gone. The message says the rule
|
|
145
|
-
* is retired, what lands a section instead, and what to do with the key.
|
|
119
|
+
* Address-scheme keys a configuration may no longer declare.
|
|
120
|
+
*
|
|
121
|
+
* A retired key has exactly two possible fates, and only one of them is honest
|
|
122
|
+
* — the same reasoning `engine/retired-fields.mjs` applies to a retired
|
|
123
|
+
* frontmatter field. Left honoured, it keeps doing whatever it did, which is
|
|
124
|
+
* why it was retired. Left *ignored*, it reads to its author as though it still
|
|
125
|
+
* works: the configuration says one thing and the build does another, and
|
|
126
|
+
* nothing says so. This module has no third option, because it has no warning
|
|
127
|
+
* channel — every finding goes through `fail()`, which throws. So a retired
|
|
128
|
+
* key is **refused**, at the line it was written on, with a message that says
|
|
129
|
+
* the mechanism is gone rather than naming a value to correct.
|
|
130
|
+
*
|
|
131
|
+
* **What `landing` did (#204).** It named which note addressed a whole section
|
|
132
|
+
* rather than a page within one — a *landing page*, which therefore had no slug
|
|
133
|
+
* of its own. #203 retired the second of its two rules and #204 retired the
|
|
134
|
+
* concept both rules chose between: a section is a Hugo content directory that
|
|
135
|
+
* the note format does not carry, a page's address names no directory, and so
|
|
136
|
+
* no note lands anything. The key outlived its mechanism by one release only
|
|
137
|
+
* because both publishing consumers still declared the then-true
|
|
138
|
+
* `landing: readme`, and neither breaking them over a correct statement nor
|
|
139
|
+
* accepting the key in silence was acceptable. Neither declares it now.
|
|
146
140
|
*
|
|
147
141
|
* @type {Readonly<Record<string, string>>}
|
|
148
142
|
*/
|
|
149
|
-
export const
|
|
150
|
-
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"the `section:` frontmatter key the rule read is retired with it",
|
|
143
|
+
export const RETIRED_ADDRESS_KEYS = Object.freeze({
|
|
144
|
+
landing:
|
|
145
|
+
"is a retired option — delete it. It named which note addressed a " +
|
|
146
|
+
"whole section rather than a page within one, and there are no " +
|
|
147
|
+
"sections to address: a section is a Hugo content directory the note " +
|
|
148
|
+
"format does not carry, so no note lands one and every page is " +
|
|
149
|
+
"addressed `<type>-<shortcode>`. Nothing replaces it",
|
|
157
150
|
});
|
|
158
151
|
|
|
159
152
|
/**
|
|
@@ -166,11 +159,12 @@ export const RETIRED_LANDING_RULES = Object.freeze({
|
|
|
166
159
|
* knowledge, held in `PACKAGE_BASE` (`engine/kb-manifest.mjs`) and prefixed at
|
|
167
160
|
* resolve time, so it is never recorded here (#1465).
|
|
168
161
|
*
|
|
169
|
-
* `landing
|
|
162
|
+
* It is the whole scheme: `landing`, the key that named which note addressed a
|
|
163
|
+
* whole section, is retired with the sections themselves — see
|
|
164
|
+
* {@link RETIRED_ADDRESS_KEYS}.
|
|
170
165
|
*/
|
|
171
166
|
export const DEFAULT_ADDRESS_SCHEME = Object.freeze({
|
|
172
167
|
prefix: "",
|
|
173
|
-
landing: "readme",
|
|
174
168
|
});
|
|
175
169
|
|
|
176
170
|
/**
|
|
@@ -471,10 +465,7 @@ export function publishesContentPages(config) {
|
|
|
471
465
|
|
|
472
466
|
/**
|
|
473
467
|
* @typedef {object} AddressSchemeInput
|
|
474
|
-
* @property {string} [prefix]
|
|
475
|
-
* @property {string} [landing] Which note addressed a whole section. Inert
|
|
476
|
-
* since #204 retired sections from the note format — see
|
|
477
|
-
* {@link LANDING_RULES}.
|
|
468
|
+
* @property {string} [prefix] Where the content tree mounts inside the package.
|
|
478
469
|
*/
|
|
479
470
|
|
|
480
471
|
/**
|
|
@@ -658,7 +649,7 @@ const SITE_KEYS = [
|
|
|
658
649
|
"backfillSections",
|
|
659
650
|
];
|
|
660
651
|
const SITE_TREE_KEYS = ["from", "section"];
|
|
661
|
-
const SECTION_META_KEYS = ["title", "banner", "description"];
|
|
652
|
+
const SECTION_META_KEYS = ["title", "banner", "description", "listType", "listSubType"];
|
|
662
653
|
const DOC_PAGE_KEYS = ["title", "out", "preamble"];
|
|
663
654
|
const RELATIONSHIP_KINDS = ["systems", "requires", "recommends", "conflicts"];
|
|
664
655
|
const RELATIONSHIP_KEYS = ["id", "type", "manifest", "compatibility", "itemCatalog"];
|
|
@@ -695,7 +686,7 @@ const STATS_KEYS = ["lastModifiedBy"];
|
|
|
695
686
|
export const DERIVED_SYSTEM_VERSION = Symbol.for("package-build.derivedSystemVersion");
|
|
696
687
|
const PUBLISH_KEYS = ["site", "manifests", "address"];
|
|
697
688
|
const MANIFEST_KEYS = ["publish", "consume"];
|
|
698
|
-
const ADDRESS_KEYS = ["prefix"
|
|
689
|
+
const ADDRESS_KEYS = ["prefix"];
|
|
699
690
|
|
|
700
691
|
/** @param {unknown} value */
|
|
701
692
|
function isPlainObject(value) {
|
|
@@ -1134,9 +1125,36 @@ function normalizeDocs(value) {
|
|
|
1134
1125
|
* add to its title. Each is left off entirely rather than written as
|
|
1135
1126
|
* `undefined`, which is not a value YAML can carry.
|
|
1136
1127
|
*
|
|
1128
|
+
* **`listType` / `listSubType` say what the section lists**
|
|
1129
|
+
* (heroiclands-hugo-theme#50). Since #204 a section's directory holds nothing
|
|
1130
|
+
* but the `_index.md` written here, so a layout reading Hugo's `.Pages` finds
|
|
1131
|
+
* no members and renders an empty landing. The membership survives in this map
|
|
1132
|
+
* and nowhere a theme can reach it, so the landing states it and a layout
|
|
1133
|
+
* substitutes the equivalent `site.RegularPages` query — the same one `sohl`'s
|
|
1134
|
+
* catalog layouts already run, which is why `sohl`'s landings never broke.
|
|
1135
|
+
*
|
|
1136
|
+
* They are two keys of their own rather than `type` / `subType` because `type`
|
|
1137
|
+
* on an `_index.md` is **Hugo's own layout selector**: verified against Hugo
|
|
1138
|
+
* 0.165, a section landing carrying `type: doc` renders through
|
|
1139
|
+
* `layouts/doc/list.html` rather than the default list template, so spelling
|
|
1140
|
+
* the content type there would silently change which template serves the
|
|
1141
|
+
* landing. (This build already uses that behaviour deliberately, for the
|
|
1142
|
+
* mount's own landing.)
|
|
1143
|
+
*
|
|
1144
|
+
* Both are checked as **address segments**, which is the trap this came from:
|
|
1145
|
+
* a section is named for the URL a consumer chose and a subType is an address
|
|
1146
|
+
* segment, and the two need not agree — `/sohl/kb/user-guide/` is the section,
|
|
1147
|
+
* `userguide` the subType (#207). Copying the section's name into the
|
|
1148
|
+
* declaration would select no page at all, and an empty landing reported by
|
|
1149
|
+
* nobody is the failure being fixed. A `listSubType` with no `listType` is
|
|
1150
|
+
* refused for the same reason: a subType is only distinguishing *within* a
|
|
1151
|
+
* type — `rules`, `userguide` and `reference` are all `doc` — so alone it names
|
|
1152
|
+
* no query.
|
|
1153
|
+
*
|
|
1137
1154
|
* @param {unknown} value - The declared entry.
|
|
1138
1155
|
* @param {string} where - Dotted path, for the error.
|
|
1139
|
-
* @returns {Readonly<{title: string, banner?: string, description?: string
|
|
1156
|
+
* @returns {Readonly<{title: string, banner?: string, description?: string,
|
|
1157
|
+
* listType?: string, listSubType?: string}>}
|
|
1140
1158
|
*/
|
|
1141
1159
|
function normalizeSectionMeta(value, where) {
|
|
1142
1160
|
if (!isPlainObject(value)) fail(where, "must be a mapping");
|
|
@@ -1149,6 +1167,32 @@ function normalizeSectionMeta(value, where) {
|
|
|
1149
1167
|
if (input.description !== undefined) {
|
|
1150
1168
|
out.description = requireNonEmptyString(input.description, `${where}.description`);
|
|
1151
1169
|
}
|
|
1170
|
+
for (const key of ["listType", "listSubType"]) {
|
|
1171
|
+
if (input[key] === undefined) continue;
|
|
1172
|
+
const segment = requireNonEmptyString(input[key], `${where}.${key}`);
|
|
1173
|
+
if (!isAddressSegment(segment)) {
|
|
1174
|
+
fail(
|
|
1175
|
+
`${where}.${key}`,
|
|
1176
|
+
`is \`${segment}\`, which is not alphanumeric. It names a ` +
|
|
1177
|
+
"content type or subType, and those are address segments " +
|
|
1178
|
+
`(${ADDRESS_SEGMENT_PATTERN.source}) — not the section's ` +
|
|
1179
|
+
"own name, which is a URL this site chose and need not " +
|
|
1180
|
+
"match (`user-guide` is the section, `userguide` the " +
|
|
1181
|
+
"subType). A value no page carries selects nothing and " +
|
|
1182
|
+
"leaves the landing empty",
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
out[key] = segment;
|
|
1186
|
+
}
|
|
1187
|
+
if (out.listSubType !== undefined && out.listType === undefined) {
|
|
1188
|
+
fail(
|
|
1189
|
+
`${where}.listSubType`,
|
|
1190
|
+
"is declared without a `listType`. A subType tells pages apart " +
|
|
1191
|
+
"only within a type — `rules`, `userguide` and `reference` " +
|
|
1192
|
+
"are all `doc` — so on its own it names no query for a layout " +
|
|
1193
|
+
"to run",
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1152
1196
|
return Object.freeze(out);
|
|
1153
1197
|
}
|
|
1154
1198
|
|
|
@@ -1731,6 +1775,15 @@ function normalizePublish(value) {
|
|
|
1731
1775
|
fail("publish.address", "must be an object");
|
|
1732
1776
|
}
|
|
1733
1777
|
const address = /** @type {Record<string, unknown>} */ (addressInput ?? {});
|
|
1778
|
+
// A retired key is refused by name, ahead of the vocabulary check: reported
|
|
1779
|
+
// as merely unrecognized it would read as a misspelling of the one key that
|
|
1780
|
+
// survives, and the author would correct the spelling rather than learn
|
|
1781
|
+
// that the mechanism is gone (#215).
|
|
1782
|
+
for (const key of Object.keys(address)) {
|
|
1783
|
+
if (Object.hasOwn(RETIRED_ADDRESS_KEYS, key)) {
|
|
1784
|
+
fail(`publish.address.${key}`, RETIRED_ADDRESS_KEYS[key]);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1734
1787
|
rejectUnknownKeys(address, ADDRESS_KEYS, "publish.address.");
|
|
1735
1788
|
|
|
1736
1789
|
const prefix =
|
|
@@ -1749,24 +1802,9 @@ function normalizePublish(value) {
|
|
|
1749
1802
|
fail("publish.address.prefix", "must not begin with a slash");
|
|
1750
1803
|
}
|
|
1751
1804
|
|
|
1752
|
-
const landing =
|
|
1753
|
-
address.landing === undefined ?
|
|
1754
|
-
DEFAULT_ADDRESS_SCHEME.landing
|
|
1755
|
-
: optionalString(address.landing, "publish.address.landing");
|
|
1756
|
-
// A retired rule is refused by name, before the vocabulary check: reported
|
|
1757
|
-
// as merely unrecognized it would read as a misspelling of the one that
|
|
1758
|
-
// survives, and the author would correct the value rather than learn that
|
|
1759
|
-
// the mechanism is gone (#202).
|
|
1760
|
-
if (Object.hasOwn(RETIRED_LANDING_RULES, landing)) {
|
|
1761
|
-
fail("publish.address.landing", RETIRED_LANDING_RULES[landing]);
|
|
1762
|
-
}
|
|
1763
|
-
if (!LANDING_RULES.includes(landing)) {
|
|
1764
|
-
fail("publish.address.landing", `must be one of ${LANDING_RULES.join(", ")}`);
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
1805
|
return Object.freeze({
|
|
1768
1806
|
site: normalizeSiteMode(publish.site),
|
|
1769
|
-
address: Object.freeze({ prefix
|
|
1807
|
+
address: Object.freeze({ prefix }),
|
|
1770
1808
|
manifests: Object.freeze({
|
|
1771
1809
|
publish: optionalBoolean(manifests.publish, "publish.manifests.publish", false),
|
|
1772
1810
|
consume: optionalBoolean(manifests.consume, "publish.manifests.consume", false),
|
package/docs/content-format.md
CHANGED
|
@@ -249,17 +249,16 @@ against the same constant a shortcode is checked against, and a note carrying a
|
|
|
249
249
|
hyphenated value is reported where it wrote it:
|
|
250
250
|
|
|
251
251
|
```text
|
|
252
|
-
Trauma/Blood_Loss.md:3:1: error: `subType` "blood-loss" is not
|
|
252
|
+
Trauma/Blood_Loss.md:3:1: error: `subType` "blood-loss" is not a well-formed subType — a subType is letters and digits only (^[A-Za-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. It is a vocabulary term the whole toolchain keys on, and one closed set away from being an address segment again, so a charset that held for every term but this one would be a rule nobody could state in a sentence
|
|
253
253
|
```
|
|
254
254
|
|
|
255
255
|
One declared value broke the rule and has been renamed: a `doc`'s `user-guide`
|
|
256
|
-
is now **`userguide`**. The old spelling
|
|
257
|
-
release
|
|
258
|
-
not
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
```
|
|
256
|
+
is now **`userguide`**. The old spelling was accepted for one transitional
|
|
257
|
+
release, as a warning naming the replacement, so the 43 `sohl` notes authoring
|
|
258
|
+
it were not invalidated by the release that renamed them. Every consumer tree
|
|
259
|
+
has swept, so the acceptance is gone (#210) and `user-guide` is refused by the
|
|
260
|
+
charset check — it contains a hyphen, which is the reason that always applied.
|
|
261
|
+
No retirement-specific code outlived the sweep.
|
|
263
262
|
|
|
264
263
|
That is a guarantee rather than an observation, and it holds: of **4,456 distinct
|
|
265
264
|
shortcodes** across the four content trees, not one contains a character outside
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
// The scheme vocabulary is part of the configuration contract — a
|
|
33
33
|
// repository names its scheme in `package-build.config.yaml` — so it is
|
|
34
34
|
// declared beside the rest of that vocabulary rather than here, and this
|
|
35
|
-
// module
|
|
36
|
-
// but `node:path` and `engine/ids.mjs`,
|
|
37
|
-
// cycle (see `engine/pack-config.mjs`).
|
|
38
|
-
import { DEFAULT_ADDRESS_SCHEME
|
|
35
|
+
// module re-exports it beside the addresses it derives. `config.mjs` is the
|
|
36
|
+
// leaf entry point and imports nothing but `node:path` and `engine/ids.mjs`,
|
|
37
|
+
// so the direction cannot close a cycle (see `engine/pack-config.mjs`).
|
|
38
|
+
import { DEFAULT_ADDRESS_SCHEME } from "../content-config.mjs";
|
|
39
39
|
|
|
40
|
-
export { DEFAULT_ADDRESS_SCHEME
|
|
40
|
+
export { DEFAULT_ADDRESS_SCHEME };
|
|
41
41
|
|
|
42
42
|
/** The knowledgebase's mount within this package's site (#1470). */
|
|
43
43
|
export const KB_PREFIX = "kb/";
|
|
@@ -116,26 +116,18 @@ export function contentAddress(fm) {
|
|
|
116
116
|
* `/<package>/api/` for generated API docs, neither of which contains a hyphen
|
|
117
117
|
* or names a type.
|
|
118
118
|
*
|
|
119
|
+
* **It takes no address scheme.** It took one until #215, to validate the
|
|
120
|
+
* `landing` rule it then discarded; with that key retired, `prefix` was the
|
|
121
|
+
* only thing left in the scheme and the paragraph above is the reason it never
|
|
122
|
+
* applied. A parameter read by nothing is the defect this deletion is about.
|
|
123
|
+
*
|
|
119
124
|
* @param {object} fm - Parsed frontmatter.
|
|
120
|
-
* @param {object} [options] - Options.
|
|
121
|
-
* @param {{prefix?: string, landing?: string}} [options.scheme] - The
|
|
122
|
-
* repository's address scheme; defaults to {@link DEFAULT_ADDRESS_SCHEME}.
|
|
123
|
-
* `landing` is validated against {@link LANDING_RULES} and selects nothing —
|
|
124
|
-
* it is accepted so a configuration declaring the still-true `landing: readme`
|
|
125
|
-
* keeps loading, and is removed once none does.
|
|
126
125
|
* @returns {string} The package-relative address, with a trailing slash and no
|
|
127
126
|
* leading one.
|
|
128
127
|
* @throws {Error} When the note has no type or no shortcode to be addressed by.
|
|
129
128
|
* Such a note is not published, and inventing an address for one would put a
|
|
130
129
|
* dead entry in the manifest.
|
|
131
130
|
*/
|
|
132
|
-
export function packageAddress(fm
|
|
133
|
-
const { landing } = { ...DEFAULT_ADDRESS_SCHEME, ...scheme };
|
|
134
|
-
if (!LANDING_RULES.includes(landing)) {
|
|
135
|
-
throw new Error(
|
|
136
|
-
`unknown landing rule ${JSON.stringify(landing)} — expected one ` +
|
|
137
|
-
`of ${LANDING_RULES.join(", ")}`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
131
|
+
export function packageAddress(fm) {
|
|
140
132
|
return `${addressSlug(fm)}/`;
|
|
141
133
|
}
|
|
@@ -61,13 +61,7 @@ import { positionInFrontmatter, positionOfFrontmatterPath } from "./diagnostics.
|
|
|
61
61
|
import { checkHomepageAddressFields } from "./homepage.mjs";
|
|
62
62
|
import { RETIRED_TYPES } from "./ids.mjs";
|
|
63
63
|
import { isAddressSegment } from "./address-charset.mjs";
|
|
64
|
-
import {
|
|
65
|
-
declaredTags,
|
|
66
|
-
retiredSubType,
|
|
67
|
-
retiredSubTypeMessage,
|
|
68
|
-
subTypeCharsetMessage,
|
|
69
|
-
typeCharsetMessage,
|
|
70
|
-
} from "./note-vocabulary.mjs";
|
|
64
|
+
import { declaredTags, subTypeCharsetMessage, typeCharsetMessage } from "./note-vocabulary.mjs";
|
|
71
65
|
import {
|
|
72
66
|
RETIRED_FIELD_ALIASES,
|
|
73
67
|
declaresRetiredAlias,
|
|
@@ -342,11 +336,18 @@ function checkDataContainer(note, { type, fields }) {
|
|
|
342
336
|
* called, and `rules`, `userguide`, `reference` mean three genres and nothing
|
|
343
337
|
* else.
|
|
344
338
|
*
|
|
345
|
-
* **
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
339
|
+
* **Two checks, in this order** — the charset, then the closed set (#206,
|
|
340
|
+
* #204). The charset is first because it is the more general statement about
|
|
341
|
+
* the same value: a value outside `^[A-Za-z0-9]+$` is refused whatever the type
|
|
342
|
+
* declares, and only once it is a well-formed term is the type's own list the
|
|
343
|
+
* reason to refuse it.
|
|
344
|
+
*
|
|
345
|
+
* There were three. #206 ran a retired-spelling check ahead of both, accepting
|
|
346
|
+
* `user-guide` as a warning naming `userguide`, so the 43 `sohl` notes
|
|
347
|
+
* authoring it were not invalidated by the release that renamed it. Every
|
|
348
|
+
* consumer tree has swept, so the acceptance guarded nothing and is gone: the
|
|
349
|
+
* old spelling now falls through to the charset check, which refuses it for the
|
|
350
|
+
* reason that always applied — it contains a hyphen (#210).
|
|
350
351
|
*
|
|
351
352
|
* @param {object} note - The note.
|
|
352
353
|
* @param {object} opts
|
|
@@ -374,26 +375,7 @@ function checkSubType(note, { type, entry }) {
|
|
|
374
375
|
];
|
|
375
376
|
}
|
|
376
377
|
|
|
377
|
-
//
|
|
378
|
-
// before the charset, because the note is not wrong about the charset in
|
|
379
|
-
// some general way — it is wrong about one value, and naming the
|
|
380
|
-
// replacement is the whole of what the author needs. A warning rather than
|
|
381
|
-
// an error for the same reason the retired field aliases below are: the
|
|
382
|
-
// note compiles to the correct page, and erroring would red every tree the
|
|
383
|
-
// moment it took this release, ahead of any chance to sweep.
|
|
384
|
-
const replacement = retiredSubType(type, value);
|
|
385
|
-
if (replacement) {
|
|
386
|
-
return [
|
|
387
|
-
{
|
|
388
|
-
file: note.file,
|
|
389
|
-
...at,
|
|
390
|
-
severity: "warning",
|
|
391
|
-
message: retiredSubTypeMessage(type, value, replacement),
|
|
392
|
-
},
|
|
393
|
-
];
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// The charset, before the closed set: a hyphenated value is unaddressable
|
|
378
|
+
// The charset, before the closed set: a value outside it is refused
|
|
397
379
|
// whatever the type declares, and the type's list is not the reason it is
|
|
398
380
|
// refused. Reported here rather than only for an enumerated type, so a
|
|
399
381
|
// `subTypes: null` type — whose values nothing may yet check — is still
|
package/engine/manifest-emit.mjs
CHANGED
|
@@ -210,8 +210,7 @@ export function entriesForNote(fm, name, address, body, ctx) {
|
|
|
210
210
|
* exist.
|
|
211
211
|
*
|
|
212
212
|
* @param {string} contentBase - Absolute path to the content tree.
|
|
213
|
-
* @param {object} ctx - `{ contentPackage, foundryPackageId, packRouter
|
|
214
|
-
* scheme }`.
|
|
213
|
+
* @param {object} ctx - `{ contentPackage, foundryPackageId, packRouter }`.
|
|
215
214
|
* @returns {{entries: Array<object>, notes: number,
|
|
216
215
|
* skipped: Array<{file: string, reason: string}>}}
|
|
217
216
|
*/
|
|
@@ -249,7 +248,7 @@ export function collectManifestEntries(contentBase, ctx) {
|
|
|
249
248
|
|
|
250
249
|
let address;
|
|
251
250
|
try {
|
|
252
|
-
address = packageAddress(fm
|
|
251
|
+
address = packageAddress(fm);
|
|
253
252
|
} catch (err) {
|
|
254
253
|
skipped.push({ file: rel, reason: err.message });
|
|
255
254
|
continue;
|
|
@@ -261,7 +260,7 @@ export function collectManifestEntries(contentBase, ctx) {
|
|
|
261
260
|
}
|
|
262
261
|
|
|
263
262
|
/**
|
|
264
|
-
* The identities
|
|
263
|
+
* The identities an emission runs against, from configuration.
|
|
265
264
|
*
|
|
266
265
|
* Resolved in one place and passed down, rather than read at each use, so the
|
|
267
266
|
* pass itself is a pure function of its context and a test can drive it without
|
|
@@ -269,15 +268,13 @@ export function collectManifestEntries(contentBase, ctx) {
|
|
|
269
268
|
*
|
|
270
269
|
* @param {object} [config] - A resolved configuration; loaded when omitted.
|
|
271
270
|
* @returns {{contentPackage: string, foundryPackageId: string, packRouter: object,
|
|
272
|
-
*
|
|
273
|
-
* skipDirectories: readonly string[]}}
|
|
271
|
+
* web: boolean, skipDirectories: readonly string[]}}
|
|
274
272
|
*/
|
|
275
273
|
export function manifestContext(config = loadPackConfig()) {
|
|
276
274
|
return {
|
|
277
275
|
contentPackage: config.contentPackage,
|
|
278
276
|
foundryPackageId: config.foundryPackage,
|
|
279
277
|
packRouter: routerFor(config),
|
|
280
|
-
scheme: config.publish.address,
|
|
281
278
|
web: publishesContentPages(config),
|
|
282
279
|
// The walk's own configuration, threaded through rather than left to
|
|
283
280
|
// its default, so a caller that passes a config drives every read.
|
|
@@ -663,11 +663,11 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
663
663
|
/* ----- core documents ------------------------------------------- */
|
|
664
664
|
|
|
665
665
|
doc: Object.freeze({
|
|
666
|
-
// `userguide`, not `user-guide`: a
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
//
|
|
666
|
+
// `userguide`, not `user-guide`: a subType is held to the address
|
|
667
|
+
// charset, and a segment carries no hyphen (#206). The old spelling was
|
|
668
|
+
// accepted transitionally for one release so the consumer trees could
|
|
669
|
+
// sweep; they have, so it is refused by the charset check now, with no
|
|
670
|
+
// retirement-specific code left over (#210).
|
|
671
671
|
subTypes: Object.freeze(["rules", "userguide", "reference"]),
|
|
672
672
|
data: Object.freeze([]),
|
|
673
673
|
}),
|
|
@@ -732,83 +732,32 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
732
732
|
});
|
|
733
733
|
|
|
734
734
|
/**
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
* Keyed by type, because a retirement is a statement about *that type's*
|
|
738
|
-
* vocabulary: `user-guide` on a `doc` is the old spelling of `userguide`, while
|
|
739
|
-
* the same string on any other type is nothing but a charset violation, and
|
|
740
|
-
* saying "did you mean userguide" there would be a guess dressed as a fact.
|
|
741
|
-
*
|
|
742
|
-
* **Recorded here rather than left in `subTypes`** so the declared list stays
|
|
743
|
-
* the list of values a note *should* write. A retired value is accepted, not
|
|
744
|
-
* declared — the difference is exactly what makes the finding possible.
|
|
745
|
-
*
|
|
746
|
-
* **Deliberately not the shape of a type rename** ({@link
|
|
747
|
-
* import("./ids.mjs").RETIRED_TYPES}), which is an error: a retired type routes
|
|
748
|
-
* a note to the wrong pack, whereas a retired subType still compiles to the
|
|
749
|
-
* correct page. The sweep is the consumer's, and the ordering is the reverse of
|
|
750
|
-
* the usual — the acceptance ships *first*, because declaring only the new
|
|
751
|
-
* spelling while 43 `sohl` notes still author the old one would invalidate all
|
|
752
|
-
* 43 with a release they had no chance to sweep ahead of. A later change
|
|
753
|
-
* removes this map, and the old spelling then falls through to the ordinary
|
|
754
|
-
* undeclared-value error with no code left to remove.
|
|
755
|
-
*
|
|
756
|
-
* @type {Readonly<Record<string, Readonly<Record<string, string>>>>}
|
|
757
|
-
*/
|
|
758
|
-
export const RETIRED_SUBTYPES = Object.freeze({
|
|
759
|
-
doc: Object.freeze({ "user-guide": "userguide" }),
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* What to write in place of a retired subType value, if it is one.
|
|
735
|
+
* What a note carrying a subType outside the address charset is told.
|
|
764
736
|
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if (!forType || !Object.hasOwn(forType, value)) return undefined;
|
|
775
|
-
return forType[value];
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* What a note carrying a retired subType is told.
|
|
737
|
+
* **Why the charset holds for a subType, which reaches no address.** #206 said
|
|
738
|
+
* "the hyphen separates the segments of an address", and that was true of a
|
|
739
|
+
* subType when it shipped: `sectionOf` returned a `doc`'s subType, so the value
|
|
740
|
+
* was a URL path segment. #204 retired sections and it is not one now. The rule
|
|
741
|
+
* stays, on its own footing: a subType is a vocabulary term the whole toolchain
|
|
742
|
+
* keys on, and it is one closed set away from being an address segment again —
|
|
743
|
+
* so the reason to spell it in the address charset is that a charset holding
|
|
744
|
+
* for a type, a shortcode and a `contentPackage` but not for a subType is a
|
|
745
|
+
* rule nobody can state in a sentence.
|
|
780
746
|
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
* @param {string} type - The note's `type`.
|
|
785
|
-
* @param {string} value - The retired spelling the note carries.
|
|
786
|
-
* @param {string} replacement - What to write instead.
|
|
787
|
-
* @returns {string} The message.
|
|
788
|
-
*/
|
|
789
|
-
export function retiredSubTypeMessage(type, value, replacement) {
|
|
790
|
-
return (
|
|
791
|
-
`\`subType\` "${value}" is a retired spelling of "${replacement}" on a ` +
|
|
792
|
-
`${type}; write "${replacement}". A subType is an address segment, and ` +
|
|
793
|
-
`a segment is ${ADDRESS_SEGMENT_PATTERN.source} — the hyphen separates ` +
|
|
794
|
-
`segments, so it can never occur inside one. The old spelling is still ` +
|
|
795
|
-
`accepted, and will stop being accepted once the trees have swept`
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* What a note carrying a subType outside the address charset is told.
|
|
747
|
+
* Contrast {@link typeCharsetMessage}, which keeps the address reasoning
|
|
748
|
+
* because a type genuinely is the first segment of every address.
|
|
801
749
|
*
|
|
802
750
|
* @param {string} value - The authored `subType`.
|
|
803
751
|
* @returns {string} The message.
|
|
804
752
|
*/
|
|
805
753
|
export function subTypeCharsetMessage(value) {
|
|
806
754
|
return (
|
|
807
|
-
`\`subType\` "${value}" is not
|
|
755
|
+
`\`subType\` "${value}" is not a well-formed subType — a subType is ` +
|
|
808
756
|
`letters and digits only (${ADDRESS_SEGMENT_PATTERN.source}), the same ` +
|
|
809
|
-
`charset a shortcode
|
|
810
|
-
`
|
|
811
|
-
`
|
|
757
|
+
`charset a type, a shortcode and a contentPackage are held to. It is a ` +
|
|
758
|
+
`vocabulary term the whole toolchain keys on, and one closed set away ` +
|
|
759
|
+
`from being an address segment again, so a charset that held for every ` +
|
|
760
|
+
`term but this one would be a rule nobody could state in a sentence`
|
|
812
761
|
);
|
|
813
762
|
}
|
|
814
763
|
|
|
@@ -836,6 +785,11 @@ export function typeCharsetMessage(type) {
|
|
|
836
785
|
* a note's bad value is one author's mistake and belongs in a report, while a
|
|
837
786
|
* bad *declaration* would tell every author to write something unaddressable.
|
|
838
787
|
*
|
|
788
|
+
* The message states the reason **per key**, as {@link typeCharsetMessage} and
|
|
789
|
+
* {@link subTypeCharsetMessage} do: a type is an address segment, and a subType
|
|
790
|
+
* has not been one since #204 retired sections, so a single claim covering both
|
|
791
|
+
* would be half wrong (#210).
|
|
792
|
+
*
|
|
839
793
|
* @param {Readonly<Record<string, TypeVocabulary>>} vocabulary - The registry.
|
|
840
794
|
* @param {string} [where] - What declares it, for the message.
|
|
841
795
|
* @throws {Error} Naming every offending type and subType at once, rather than
|
|
@@ -854,9 +808,13 @@ export function assertVocabularyCharset(vocabulary, where = "the note vocabulary
|
|
|
854
808
|
if (!bad.length) return;
|
|
855
809
|
throw new Error(
|
|
856
810
|
`${where} declares ${bad.join(", ")}, which ${bad.length === 1 ? "is" : "are"} ` +
|
|
857
|
-
`not ${ADDRESS_SEGMENT_PATTERN.source}. A type
|
|
858
|
-
`
|
|
859
|
-
`
|
|
811
|
+
`not ${ADDRESS_SEGMENT_PATTERN.source}. A type is an address segment, ` +
|
|
812
|
+
`and the hyphen separates segments rather than occurring inside one. ` +
|
|
813
|
+
`A subType reaches no address since #204 retired sections, and is ` +
|
|
814
|
+
`held to the same charset anyway: it is a vocabulary term the whole ` +
|
|
815
|
+
`toolchain keys on, one closed set away from being a segment again, ` +
|
|
816
|
+
`and a charset holding for every term but that one would be a rule ` +
|
|
817
|
+
`nobody could state in a sentence.`,
|
|
860
818
|
);
|
|
861
819
|
}
|
|
862
820
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -56,36 +56,31 @@ export namespace DEFAULT_PATHS {
|
|
|
56
56
|
*/
|
|
57
57
|
export const PACK_DOCUMENT_TYPES: readonly ["Actor", "Adventure", "Item", "JournalEntry", "Macro", "Scene"];
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* A
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* A retired *value* is refused the way a retired *field* is (see
|
|
81
|
-
* `engine/retired-fields.mjs`): left merely unrecognized it would be reported
|
|
82
|
-
* as a bad value, which names something to correct and leaves the author to
|
|
83
|
-
* work out for themselves that the mechanism is gone. The message says the rule
|
|
84
|
-
* is retired, what lands a section instead, and what to do with the key.
|
|
59
|
+
* Address-scheme keys a configuration may no longer declare.
|
|
60
|
+
*
|
|
61
|
+
* A retired key has exactly two possible fates, and only one of them is honest
|
|
62
|
+
* — the same reasoning `engine/retired-fields.mjs` applies to a retired
|
|
63
|
+
* frontmatter field. Left honoured, it keeps doing whatever it did, which is
|
|
64
|
+
* why it was retired. Left *ignored*, it reads to its author as though it still
|
|
65
|
+
* works: the configuration says one thing and the build does another, and
|
|
66
|
+
* nothing says so. This module has no third option, because it has no warning
|
|
67
|
+
* channel — every finding goes through `fail()`, which throws. So a retired
|
|
68
|
+
* key is **refused**, at the line it was written on, with a message that says
|
|
69
|
+
* the mechanism is gone rather than naming a value to correct.
|
|
70
|
+
*
|
|
71
|
+
* **What `landing` did (#204).** It named which note addressed a whole section
|
|
72
|
+
* rather than a page within one — a *landing page*, which therefore had no slug
|
|
73
|
+
* of its own. #203 retired the second of its two rules and #204 retired the
|
|
74
|
+
* concept both rules chose between: a section is a Hugo content directory that
|
|
75
|
+
* the note format does not carry, a page's address names no directory, and so
|
|
76
|
+
* no note lands anything. The key outlived its mechanism by one release only
|
|
77
|
+
* because both publishing consumers still declared the then-true
|
|
78
|
+
* `landing: readme`, and neither breaking them over a correct statement nor
|
|
79
|
+
* accepting the key in silence was acceptable. Neither declares it now.
|
|
85
80
|
*
|
|
86
81
|
* @type {Readonly<Record<string, string>>}
|
|
87
82
|
*/
|
|
88
|
-
export const
|
|
83
|
+
export const RETIRED_ADDRESS_KEYS: Readonly<Record<string, string>>;
|
|
89
84
|
/**
|
|
90
85
|
* A repository's address scheme, with the defaults an unconfigured one gets.
|
|
91
86
|
*
|
|
@@ -96,11 +91,12 @@ export const RETIRED_LANDING_RULES: Readonly<Record<string, string>>;
|
|
|
96
91
|
* knowledge, held in `PACKAGE_BASE` (`engine/kb-manifest.mjs`) and prefixed at
|
|
97
92
|
* resolve time, so it is never recorded here (#1465).
|
|
98
93
|
*
|
|
99
|
-
* `landing
|
|
94
|
+
* It is the whole scheme: `landing`, the key that named which note addressed a
|
|
95
|
+
* whole section, is retired with the sections themselves — see
|
|
96
|
+
* {@link RETIRED_ADDRESS_KEYS}.
|
|
100
97
|
*/
|
|
101
98
|
export const DEFAULT_ADDRESS_SCHEME: Readonly<{
|
|
102
99
|
prefix: "";
|
|
103
|
-
landing: "readme";
|
|
104
100
|
}>;
|
|
105
101
|
/**
|
|
106
102
|
* How much of a package reaches the web.
|
|
@@ -515,12 +511,6 @@ export type AddressSchemeInput = {
|
|
|
515
511
|
* Where the content tree mounts inside the package.
|
|
516
512
|
*/
|
|
517
513
|
prefix?: string | undefined;
|
|
518
|
-
/**
|
|
519
|
-
* Which note addressed a whole section. Inert
|
|
520
|
-
* since #204 retired sections from the note format — see
|
|
521
|
-
* {@link LANDING_RULES}.
|
|
522
|
-
*/
|
|
523
|
-
landing?: string | undefined;
|
|
524
514
|
};
|
|
525
515
|
/**
|
|
526
516
|
* One entry of a consumer's `itemBuilders` registry.
|
|
@@ -55,27 +55,20 @@ export function contentAddress(fm: object): string;
|
|
|
55
55
|
* `/<package>/api/` for generated API docs, neither of which contains a hyphen
|
|
56
56
|
* or names a type.
|
|
57
57
|
*
|
|
58
|
+
* **It takes no address scheme.** It took one until #215, to validate the
|
|
59
|
+
* `landing` rule it then discarded; with that key retired, `prefix` was the
|
|
60
|
+
* only thing left in the scheme and the paragraph above is the reason it never
|
|
61
|
+
* applied. A parameter read by nothing is the defect this deletion is about.
|
|
62
|
+
*
|
|
58
63
|
* @param {object} fm - Parsed frontmatter.
|
|
59
|
-
* @param {object} [options] - Options.
|
|
60
|
-
* @param {{prefix?: string, landing?: string}} [options.scheme] - The
|
|
61
|
-
* repository's address scheme; defaults to {@link DEFAULT_ADDRESS_SCHEME}.
|
|
62
|
-
* `landing` is validated against {@link LANDING_RULES} and selects nothing —
|
|
63
|
-
* it is accepted so a configuration declaring the still-true `landing: readme`
|
|
64
|
-
* keeps loading, and is removed once none does.
|
|
65
64
|
* @returns {string} The package-relative address, with a trailing slash and no
|
|
66
65
|
* leading one.
|
|
67
66
|
* @throws {Error} When the note has no type or no shortcode to be addressed by.
|
|
68
67
|
* Such a note is not published, and inventing an address for one would put a
|
|
69
68
|
* dead entry in the manifest.
|
|
70
69
|
*/
|
|
71
|
-
export function packageAddress(fm: object
|
|
72
|
-
|
|
73
|
-
prefix?: string;
|
|
74
|
-
landing?: string;
|
|
75
|
-
} | undefined;
|
|
76
|
-
}): string;
|
|
70
|
+
export function packageAddress(fm: object): string;
|
|
71
|
+
export { DEFAULT_ADDRESS_SCHEME };
|
|
77
72
|
/** The knowledgebase's mount within this package's site (#1470). */
|
|
78
73
|
export const KB_PREFIX: "kb/";
|
|
79
74
|
import { DEFAULT_ADDRESS_SCHEME } from "../content-config.mjs";
|
|
80
|
-
import { LANDING_RULES } from "../content-config.mjs";
|
|
81
|
-
export { DEFAULT_ADDRESS_SCHEME, LANDING_RULES };
|
|
@@ -51,8 +51,7 @@ export function entriesForNote(fm: object, name: string, address: string, body:
|
|
|
51
51
|
* exist.
|
|
52
52
|
*
|
|
53
53
|
* @param {string} contentBase - Absolute path to the content tree.
|
|
54
|
-
* @param {object} ctx - `{ contentPackage, foundryPackageId, packRouter
|
|
55
|
-
* scheme }`.
|
|
54
|
+
* @param {object} ctx - `{ contentPackage, foundryPackageId, packRouter }`.
|
|
56
55
|
* @returns {{entries: Array<object>, notes: number,
|
|
57
56
|
* skipped: Array<{file: string, reason: string}>}}
|
|
58
57
|
*/
|
|
@@ -65,7 +64,7 @@ export function collectManifestEntries(contentBase: string, ctx: object): {
|
|
|
65
64
|
}>;
|
|
66
65
|
};
|
|
67
66
|
/**
|
|
68
|
-
* The identities
|
|
67
|
+
* The identities an emission runs against, from configuration.
|
|
69
68
|
*
|
|
70
69
|
* Resolved in one place and passed down, rather than read at each use, so the
|
|
71
70
|
* pass itself is a pure function of its context and a test can drive it without
|
|
@@ -73,17 +72,12 @@ export function collectManifestEntries(contentBase: string, ctx: object): {
|
|
|
73
72
|
*
|
|
74
73
|
* @param {object} [config] - A resolved configuration; loaded when omitted.
|
|
75
74
|
* @returns {{contentPackage: string, foundryPackageId: string, packRouter: object,
|
|
76
|
-
*
|
|
77
|
-
* skipDirectories: readonly string[]}}
|
|
75
|
+
* web: boolean, skipDirectories: readonly string[]}}
|
|
78
76
|
*/
|
|
79
77
|
export function manifestContext(config?: object): {
|
|
80
78
|
contentPackage: string;
|
|
81
79
|
foundryPackageId: string;
|
|
82
80
|
packRouter: object;
|
|
83
|
-
scheme: {
|
|
84
|
-
prefix: string;
|
|
85
|
-
landing: string;
|
|
86
|
-
};
|
|
87
81
|
web: boolean;
|
|
88
82
|
skipDirectories: readonly string[];
|
|
89
83
|
};
|
|
@@ -36,30 +36,20 @@ export function hasTag(fm: object | null | undefined, tag: string): boolean;
|
|
|
36
36
|
*/
|
|
37
37
|
export function isDraftNote(fm: object | null | undefined): boolean;
|
|
38
38
|
/**
|
|
39
|
-
* What
|
|
40
|
-
*
|
|
41
|
-
* @param {string} type - The note's `type`.
|
|
42
|
-
* @param {string} value - The authored `subType`.
|
|
43
|
-
* @param {Readonly<Record<string, Readonly<Record<string, string>>>>} [retired]
|
|
44
|
-
* The map to read, defaulting to {@link RETIRED_SUBTYPES}.
|
|
45
|
-
* @returns {string|undefined} The current spelling, or `undefined` when the
|
|
46
|
-
* value is not a retired one — which is not the same as it being valid.
|
|
47
|
-
*/
|
|
48
|
-
export function retiredSubType(type: string, value: string, retired?: Readonly<Record<string, Readonly<Record<string, string>>>>): string | undefined;
|
|
49
|
-
/**
|
|
50
|
-
* What a note carrying a retired subType is told.
|
|
39
|
+
* What a note carrying a subType outside the address charset is told.
|
|
51
40
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
41
|
+
* **Why the charset holds for a subType, which reaches no address.** #206 said
|
|
42
|
+
* "the hyphen separates the segments of an address", and that was true of a
|
|
43
|
+
* subType when it shipped: `sectionOf` returned a `doc`'s subType, so the value
|
|
44
|
+
* was a URL path segment. #204 retired sections and it is not one now. The rule
|
|
45
|
+
* stays, on its own footing: a subType is a vocabulary term the whole toolchain
|
|
46
|
+
* keys on, and it is one closed set away from being an address segment again —
|
|
47
|
+
* so the reason to spell it in the address charset is that a charset holding
|
|
48
|
+
* for a type, a shortcode and a `contentPackage` but not for a subType is a
|
|
49
|
+
* rule nobody can state in a sentence.
|
|
54
50
|
*
|
|
55
|
-
* @
|
|
56
|
-
*
|
|
57
|
-
* @param {string} replacement - What to write instead.
|
|
58
|
-
* @returns {string} The message.
|
|
59
|
-
*/
|
|
60
|
-
export function retiredSubTypeMessage(type: string, value: string, replacement: string): string;
|
|
61
|
-
/**
|
|
62
|
-
* What a note carrying a subType outside the address charset is told.
|
|
51
|
+
* Contrast {@link typeCharsetMessage}, which keeps the address reasoning
|
|
52
|
+
* because a type genuinely is the first segment of every address.
|
|
63
53
|
*
|
|
64
54
|
* @param {string} value - The authored `subType`.
|
|
65
55
|
* @returns {string} The message.
|
|
@@ -80,6 +70,11 @@ export function typeCharsetMessage(type: string): string;
|
|
|
80
70
|
* a note's bad value is one author's mistake and belongs in a report, while a
|
|
81
71
|
* bad *declaration* would tell every author to write something unaddressable.
|
|
82
72
|
*
|
|
73
|
+
* The message states the reason **per key**, as {@link typeCharsetMessage} and
|
|
74
|
+
* {@link subTypeCharsetMessage} do: a type is an address segment, and a subType
|
|
75
|
+
* has not been one since #204 retired sections, so a single claim covering both
|
|
76
|
+
* would be half wrong (#210).
|
|
77
|
+
*
|
|
83
78
|
* @param {Readonly<Record<string, TypeVocabulary>>} vocabulary - The registry.
|
|
84
79
|
* @param {string} [where] - What declares it, for the message.
|
|
85
80
|
* @throws {Error} Naming every offending type and subType at once, rather than
|
|
@@ -288,31 +283,6 @@ export const NOTE_VOCABULARY: Readonly<{
|
|
|
288
283
|
})[];
|
|
289
284
|
}>;
|
|
290
285
|
}>;
|
|
291
|
-
/**
|
|
292
|
-
* The retired spelling of a subType a type declares → what to write now (#206).
|
|
293
|
-
*
|
|
294
|
-
* Keyed by type, because a retirement is a statement about *that type's*
|
|
295
|
-
* vocabulary: `user-guide` on a `doc` is the old spelling of `userguide`, while
|
|
296
|
-
* the same string on any other type is nothing but a charset violation, and
|
|
297
|
-
* saying "did you mean userguide" there would be a guess dressed as a fact.
|
|
298
|
-
*
|
|
299
|
-
* **Recorded here rather than left in `subTypes`** so the declared list stays
|
|
300
|
-
* the list of values a note *should* write. A retired value is accepted, not
|
|
301
|
-
* declared — the difference is exactly what makes the finding possible.
|
|
302
|
-
*
|
|
303
|
-
* **Deliberately not the shape of a type rename** ({@link
|
|
304
|
-
* import("./ids.mjs").RETIRED_TYPES}), which is an error: a retired type routes
|
|
305
|
-
* a note to the wrong pack, whereas a retired subType still compiles to the
|
|
306
|
-
* correct page. The sweep is the consumer's, and the ordering is the reverse of
|
|
307
|
-
* the usual — the acceptance ships *first*, because declaring only the new
|
|
308
|
-
* spelling while 43 `sohl` notes still author the old one would invalidate all
|
|
309
|
-
* 43 with a release they had no chance to sweep ahead of. A later change
|
|
310
|
-
* removes this map, and the old spelling then falls through to the ordinary
|
|
311
|
-
* undeclared-value error with no code left to remove.
|
|
312
|
-
*
|
|
313
|
-
* @type {Readonly<Record<string, Readonly<Record<string, string>>>>}
|
|
314
|
-
*/
|
|
315
|
-
export const RETIRED_SUBTYPES: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
316
286
|
/**
|
|
317
287
|
* One `data:` key a note type may carry.
|
|
318
288
|
*
|