@heroiclands/package-build 3.3.0 → 4.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 +179 -0
- package/CONTENT.md +29 -12
- package/MIGRATING.md +47 -0
- package/engine/base-compiler.mjs +33 -36
- package/engine/content-links.mjs +15 -13
- package/engine/content-package.mjs +3 -2
- package/engine/field-reference.mjs +2 -2
- package/engine/frontmatter-lint.mjs +26 -0
- package/engine/helpers.mjs +10 -12
- package/engine/index.mjs +4 -1
- package/engine/journals.mjs +2 -3
- package/engine/macros.mjs +2 -3
- package/engine/manifest-emit.mjs +15 -11
- package/engine/note-package.mjs +75 -68
- package/engine/pack-router.mjs +3 -3
- package/engine/retired-fields.mjs +123 -0
- package/engine/scenes.mjs +7 -11
- package/engine/site-build.mjs +30 -11
- package/engine/site-index.mjs +5 -5
- package/package.json +1 -1
- package/sohl/actors.mjs +2 -3
- package/sohl/item-fields.mjs +0 -8
- package/sohl/items.mjs +2 -3
- package/types/engine/base-compiler.d.mts +12 -17
- package/types/engine/content-package.d.mts +3 -2
- package/types/engine/helpers.d.mts +5 -8
- package/types/engine/index.d.mts +1 -0
- package/types/engine/manifest-emit.d.mts +7 -8
- package/types/engine/note-package.d.mts +29 -34
- package/types/engine/pack-router.d.mts +3 -3
- package/types/engine/retired-fields.d.mts +54 -0
- package/types/engine/site-build.d.mts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,184 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 8cfa834: Reject `package:` in a note's frontmatter (#56).
|
|
8
|
+
|
|
9
|
+
A note's package is the repository's configured `contentPackage`, full stop. A
|
|
10
|
+
note that declares the field fails the build, naming the file — **whatever the
|
|
11
|
+
value says**. An agreeing declaration is refused exactly as a disagreeing one
|
|
12
|
+
is: there is no value that makes writing the field correct, and a field accepted
|
|
13
|
+
while it agrees is a field that grows back one note at a time.
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter field — delete it. A note's package is this repository's configured `contentPackage` ("sohl", in package-build.config.yaml), and every note in the tree belongs to it.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`content-build lint` reports every such note in one pass, so a tree can be
|
|
20
|
+
checked before it is compiled; `package compile` and `manifest` refuse it.
|
|
21
|
+
|
|
22
|
+
**This is the third and last step**, and the two before it are already released
|
|
23
|
+
and adopted. 3.3.0 made the field optional so an absent one was normal and a
|
|
24
|
+
disagreeing one was an error; every content tree on the org was then swept on
|
|
25
|
+
that version — `sohl` (1,639 files), `thalorna` (1,716), `kethira` (363),
|
|
26
|
+
`harnensemble` (2,517). Nothing this release refuses is authored anywhere today.
|
|
27
|
+
|
|
28
|
+
**Why a major.** Consumers resolve `^3`, so a minor would reach every repository
|
|
29
|
+
on the next Dependabot run. A major is adopted deliberately, one repository at a
|
|
30
|
+
time, in a pull request that can also delete the field if any grew back — which
|
|
31
|
+
is the whole mechanism that made the deprecate → migrate → remove sequence safe.
|
|
32
|
+
|
|
33
|
+
**Migrating** is one line, and nothing else reads the field:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`package compile` then produces byte-identical output, because the value the
|
|
40
|
+
build derives is the value the notes restated. See `MIGRATING.md`.
|
|
41
|
+
|
|
42
|
+
**`contentPackage` is unaffected, and is not vestigial.** It is the address
|
|
43
|
+
namespace — the first segment of every canonical key, the name of the emitted
|
|
44
|
+
link manifest, and the package a cross-package wikilink writes. Retiring the
|
|
45
|
+
frontmatter field is what leaves it as the single source of that value: every
|
|
46
|
+
key is now derived from the configuration, where it used to come from two
|
|
47
|
+
sources that happened to agree.
|
|
48
|
+
|
|
49
|
+
**A generated table's `WHERE … and package = "<pkg>"` clause keeps matching** —
|
|
50
|
+
45 such clauses across `sohl` and `thalorna` depend on it. The package is
|
|
51
|
+
_synthesised_ into what the table search sees, from `contentPackage`; it is a
|
|
52
|
+
search value, never an authored one.
|
|
53
|
+
|
|
54
|
+
**API.** `engine/note-package.mjs` no longer exports `notePackage` — every call
|
|
55
|
+
site takes `contentPackage` directly, so no key is derived from frontmatter
|
|
56
|
+
anywhere — and `assertNotePackage` is now `assertNoDeclaredPackage`, which
|
|
57
|
+
asserts the field's absence rather than answering which package a note belongs
|
|
58
|
+
to. `expandNoteTables` no longer takes `pkg`: a table searches the whole tree,
|
|
59
|
+
which is one package's notes and nothing else.
|
|
60
|
+
- ad7691f: Remove `draft:` from a note's frontmatter (#69).
|
|
61
|
+
|
|
62
|
+
The field excluded a note from the compiled packs, from the link manifest and
|
|
63
|
+
from a consuming site build — and **nothing reported the consequence**.
|
|
64
|
+
`content-links.mjs`, `site-index.mjs` and `content-lint.mjs` never read it, so a
|
|
65
|
+
wikilink into a drafted note was indistinguishable from a link to a note that
|
|
66
|
+
does not exist, and no checker could say which. Its entire effect was to move a
|
|
67
|
+
note from _published_ to _unresolvable_, in silence. It also suppressed real
|
|
68
|
+
build failures: a note the compilers never reached could not fail on the defects
|
|
69
|
+
it carried.
|
|
70
|
+
|
|
71
|
+
Nothing used it. Across every HeroicLands content repository — `sohl`,
|
|
72
|
+
`sohl-thalorna`, `sohl-kethira-basic`, `harn-ensemble`, `harn-adventures` — not
|
|
73
|
+
one note declared it.
|
|
74
|
+
|
|
75
|
+
**What changed**
|
|
76
|
+
|
|
77
|
+
- The three readers are gone: the compile loop, the link-manifest walk, and the
|
|
78
|
+
scenes pass's map collection. So are the `skippedDraft` tally, its `PassStats`
|
|
79
|
+
field, and the `Skipped N draft(s)` log line.
|
|
80
|
+
- A note declaring `draft:` now **fails the build**, naming the file and the
|
|
81
|
+
line, whatever the value says — `draft: false` included, since it reads as
|
|
82
|
+
"publish this note", which is what happens either way. A field left merely
|
|
83
|
+
ignored reads to its author as though it still works, which is the same
|
|
84
|
+
silence in a different place.
|
|
85
|
+
- `content-build lint` reports it too, so a whole tree is answered at once
|
|
86
|
+
rather than one note per build.
|
|
87
|
+
|
|
88
|
+
**The `draft` _tag_ is untouched.** It is an authoring marker, read only by the
|
|
89
|
+
generated-table pass for `FROM #draft` queries, and 268 `sohl-thalorna` notes
|
|
90
|
+
carry it. An unfinished page is honest about being unfinished; a dropped link is
|
|
91
|
+
silent.
|
|
92
|
+
|
|
93
|
+
**Adopting**
|
|
94
|
+
|
|
95
|
+
Nothing to sweep — no note in the org declares the field. A consumer that
|
|
96
|
+
carries one deletes the line.
|
|
97
|
+
|
|
98
|
+
This lands in the same major as the `package:` rejection (#56 step 3), so the
|
|
99
|
+
two retired fields are one adoption rather than two. They are refused the same
|
|
100
|
+
way, through the same diagnostic format and the same positioning, and the
|
|
101
|
+
locator both need is now shared rather than written twice.
|
|
102
|
+
|
|
103
|
+
## 3.4.0
|
|
104
|
+
|
|
105
|
+
### Minor Changes
|
|
106
|
+
|
|
107
|
+
- 19df269: Stop emitting `assocMysteryCode` on a compiled mystical ability (#35).
|
|
108
|
+
|
|
109
|
+
The `mysticalability` declaration named a field no SoHL DataModel receives.
|
|
110
|
+
`MysticalAbilityDataModel` declares `subType`, `assocSkillCode`,
|
|
111
|
+
`assocAffiliationCode`, `masteryLevelBase`, `improveFlag`, `levelBase` and
|
|
112
|
+
`charges`, and nothing else — Foundry discards the extra key when the document
|
|
113
|
+
is constructed, so every mystical ability in every consuming pack shipped a
|
|
114
|
+
value that was thrown away at load, with nothing at compile or load time saying
|
|
115
|
+
so.
|
|
116
|
+
|
|
117
|
+
This is the exact inverse of #3 and has the same root cause: nothing compares a
|
|
118
|
+
builder's emitted `system` block against the DataModel that receives it. #3 was a
|
|
119
|
+
declared field the builder failed to emit; this is an emitted field the DataModel
|
|
120
|
+
never declares. Both compile clean, both lose data silently, and an author cannot
|
|
121
|
+
tell either from a correct build. The general check is #60.
|
|
122
|
+
|
|
123
|
+
**The field was retired, not renamed.**
|
|
124
|
+
Song-of-Heroic-Lands-FoundryVTT#973 deleted `assocMysteryCode` because nothing in
|
|
125
|
+
production read the mystery it resolved to; #1012 later added
|
|
126
|
+
`assocAffiliationCode` as a separate concept — the faction whose standing confers
|
|
127
|
+
the ability. They look alike and mean different things, so the declaration is
|
|
128
|
+
dropped rather than retargeted.
|
|
129
|
+
|
|
130
|
+
**This changes emitted documents**, so a consumer wants a rebuild rather than a
|
|
131
|
+
silent upgrade — though nothing downstream can have depended on the value:
|
|
132
|
+
|
|
133
|
+
- `sohl` — nine notes authored the key, all of them blank. Corrected in
|
|
134
|
+
Song-of-Heroic-Lands-FoundryVTT#1747.
|
|
135
|
+
- `sohl-kethira-basic` — no note authors it; all 224 mystical abilities carried
|
|
136
|
+
the builder's own `""`. Recompiling `main` with this change removes exactly
|
|
137
|
+
those 224 lines and touches nothing else.
|
|
138
|
+
- dced8b2: Write the derived package into an emitted page's frontmatter (#65).
|
|
139
|
+
|
|
140
|
+
`content-build site` copied a note's frontmatter to the page verbatim. Since
|
|
141
|
+
3.3.0 a note need not declare `package:` — it is derived from the configured
|
|
142
|
+
`contentPackage` — and `engine/site-build.mjs` resolved that value one line
|
|
143
|
+
before it built the page, carrying it for the index, the table universe and the
|
|
144
|
+
local-package set. It never reached the page's own frontmatter, so a swept tree
|
|
145
|
+
published pages that said nothing about which package they belong to.
|
|
146
|
+
|
|
147
|
+
**The visible symptom is the breadcrumb.**
|
|
148
|
+
`@heroiclands/hugo-theme`'s `layouts/partials/breadcrumbs.html` reads
|
|
149
|
+
`{{ $pkg := .Params.package | default "" }}` and builds the middle crumb from
|
|
150
|
+
it. With no `package` the section is never resolved and the crumb degrades from
|
|
151
|
+
a linked, labelled section to a bare, unlinked type slug:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
before: Home > SoHL Affliction > Aconite (linked)
|
|
155
|
+
after: Home > affliction > Aconite (bare)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Consumer layouts reading the field directly degrade the same way — a `package`
|
|
159
|
+
column renders blank.
|
|
160
|
+
|
|
161
|
+
The fix is where the value was already known: `pageFrontmatter` spreads
|
|
162
|
+
`package` after the note's own frontmatter, so a note that declares the field
|
|
163
|
+
keeps its authored position and value and an unswept tree emits byte-identically,
|
|
164
|
+
while a swept one regains the line it lost. The alternative — teaching every
|
|
165
|
+
theme and consumer layout to default the package from a site parameter — pushes
|
|
166
|
+
a fact the build already knows out to N consumers, and the theme deliberately
|
|
167
|
+
carries no addresses.
|
|
168
|
+
|
|
169
|
+
**This changes emitted output**, so a consumer wants a rebuild rather than a
|
|
170
|
+
silent upgrade, which is why it is a minor rather than a patch — the same
|
|
171
|
+
reasoning as #35. Verified against the swept `sohl` tree: 1,606 emitted content
|
|
172
|
+
pages differ, none added or removed, and the only diff line class across the
|
|
173
|
+
whole tree is the restored `package: sohl`. Rendering that tree, 1,600 pages
|
|
174
|
+
differ and in exactly two ways — the breadcrumb's middle crumb, and a `package`
|
|
175
|
+
column that was blank.
|
|
176
|
+
|
|
177
|
+
`sohl-thalorna` has the same defect independently, in its own
|
|
178
|
+
`utils/build-site-content.mjs`, and is fixed in that repository
|
|
179
|
+
(sohl-thalorna#79) — two emitters, one behaviour, which is a second argument
|
|
180
|
+
for #36.
|
|
181
|
+
|
|
3
182
|
## 3.3.0
|
|
4
183
|
|
|
5
184
|
### Minor Changes
|
package/CONTENT.md
CHANGED
|
@@ -178,27 +178,44 @@ something migrates on it.
|
|
|
178
178
|
### A note's package is the repository's, not the note's
|
|
179
179
|
|
|
180
180
|
`contentPackage` is the **address namespace** every note in the tree is
|
|
181
|
-
published under
|
|
181
|
+
published under: the first segment of every canonical key (`sohl-skill-clmb`),
|
|
182
|
+
the name of the link manifest this build emits (`sohl.json`), and the package a
|
|
183
|
+
cross-package wikilink writes to reach one of these notes. It is the
|
|
184
|
+
repository's identity in the address space — not a filter — and a note does not
|
|
185
|
+
restate it.
|
|
186
|
+
|
|
187
|
+
**`package:` in a note's frontmatter is retired, and declaring it fails the
|
|
188
|
+
build**, naming the file, whatever the value says. An agreeing declaration is
|
|
189
|
+
refused exactly as a disagreeing one is: there is no value that makes writing
|
|
190
|
+
the field correct. The diagnostic says so, and says where the value comes from
|
|
191
|
+
instead:
|
|
182
192
|
|
|
183
|
-
|
|
184
|
-
is
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
```text
|
|
194
|
+
assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter field — delete it. A note's package is this repository's configured `contentPackage` ("sohl", in package-build.config.yaml), and every note in the tree belongs to it.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`content-build lint` reports every such note in one pass; `content-build
|
|
198
|
+
package compile` and `content-build manifest` refuse the tree.
|
|
189
199
|
|
|
190
200
|
A generated table that scopes itself with `WHERE … and package = "<pkg>"` keeps
|
|
191
|
-
working
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
working: the package is **synthesised** into what the table search sees,
|
|
202
|
+
supplied from `contentPackage` rather than read off the note. It is a search
|
|
203
|
+
value, never an authored one.
|
|
194
204
|
|
|
195
205
|
It used to **select**: a note compiled when its `package:` matched and was
|
|
196
206
|
skipped when it did not. Every content tree is single-package — each is
|
|
197
207
|
single-sourced in the repository that ships it — so the field restated one
|
|
198
208
|
constant thousands of times, while a tree whose notes named a package no
|
|
199
209
|
configuration answered to compiled **zero notes and exited 0**. Deleting the
|
|
200
|
-
field from a note is
|
|
201
|
-
|
|
210
|
+
field from a note is the fix; deleting the _configured_ value is not, since
|
|
211
|
+
every address derives from it.
|
|
212
|
+
|
|
213
|
+
Sweeping a tree is mechanical — the field is a whole line, and nothing else
|
|
214
|
+
reads it:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
|
|
218
|
+
```
|
|
202
219
|
|
|
203
220
|
### A registry of your own
|
|
204
221
|
|
package/MIGRATING.md
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
# Migrating to `@heroiclands/package-build` 4.0.0
|
|
2
|
+
|
|
3
|
+
**One authoring change: delete `package:` from every content note.** A note's
|
|
4
|
+
package is the repository's configured `contentPackage`, and declaring the field
|
|
5
|
+
is now a build error rather than a redundancy the build tolerated.
|
|
6
|
+
|
|
7
|
+
## 1. Sweep the content tree
|
|
8
|
+
|
|
9
|
+
The field is a whole line, and nothing else reads it:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
(GNU `sed`: `sed -i '/^package: /d'`.) Then compile — `content-build package
|
|
16
|
+
compile` must produce byte-identical output to the run before the sweep, because
|
|
17
|
+
the value the build derives is the value the notes restated.
|
|
18
|
+
|
|
19
|
+
A note that still carries the field fails the build where it is:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter field — delete it. A note's package is this repository's configured `contentPackage` ("sohl", in package-build.config.yaml), and every note in the tree belongs to it.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`content-build lint` reports every one of them in a single pass, so the sweep can
|
|
26
|
+
be checked before it is compiled.
|
|
27
|
+
|
|
28
|
+
## 2. Nothing else
|
|
29
|
+
|
|
30
|
+
- **`contentPackage` stays**, and is unchanged. It is the address namespace —
|
|
31
|
+
the first segment of every canonical key, the name of the emitted link
|
|
32
|
+
manifest, and the package a cross-package wikilink writes. Every address in
|
|
33
|
+
every manifest is identical across this upgrade.
|
|
34
|
+
- **A generated table's `WHERE … and package = "<pkg>"` clause keeps matching.**
|
|
35
|
+
The package is synthesised into what the table search sees, from
|
|
36
|
+
`contentPackage`; it was never the authored field that answered the clause
|
|
37
|
+
after 3.3.0.
|
|
38
|
+
- **No configuration key changed**, and no CLI command, flag or exit code.
|
|
39
|
+
|
|
40
|
+
## What this replaced
|
|
41
|
+
|
|
42
|
+
A note used to be _selected_ by the field: it compiled when `package:` matched
|
|
43
|
+
`contentPackage` and was skipped, silently and as "belongs to another pass",
|
|
44
|
+
when it did not. A tree whose notes named a package no configuration answered to
|
|
45
|
+
compiled **zero notes and exited 0** (#56). 3.3.0 made the field optional so
|
|
46
|
+
every repository could be swept on a non-breaking version; this major removes it.
|
|
47
|
+
|
|
1
48
|
# Migrating to `@heroiclands/package-build` 3.0.0
|
|
2
49
|
|
|
3
50
|
`@heroiclands/content-build` and `@heroiclands/package-build` are one package.
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* `BasePackCompiler` — the one compile loop every pack pass runs.
|
|
16
16
|
*
|
|
17
|
-
* Walking the content tree, rejecting what this build does not own,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Walking the content tree, rejecting what this build does not own, expanding
|
|
18
|
+
* generated tables, converting wikilinks, writing the JSON and counting what
|
|
19
|
+
* failed are the same in every pass. They were written out
|
|
20
20
|
* once per pass — three times when this was filed, five by the time it landed —
|
|
21
21
|
* so a fix to any of them had to be made everywhere, and the passes drifted
|
|
22
22
|
* apart in exactly the places nobody was comparing (#1509).
|
|
@@ -73,26 +73,25 @@ import {
|
|
|
73
73
|
expandNoteTables,
|
|
74
74
|
} from "./helpers.mjs";
|
|
75
75
|
import { emitDiagnostic } from "./diagnostics.mjs";
|
|
76
|
-
import {
|
|
77
|
-
import {
|
|
76
|
+
import { assertNoDeclaredPackage } from "./note-package.mjs";
|
|
77
|
+
import { assertNoDraftField } from "./retired-fields.mjs";
|
|
78
78
|
import { assertTypeNotRetired, packForType } from "./ids.mjs";
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* The tallies one pass accumulates while walking the tree.
|
|
82
82
|
*
|
|
83
83
|
* `declined` and `skippedOther` are deliberately separate numbers. A declined
|
|
84
|
-
* note is one this build **refused** — it
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
84
|
+
* note is one this build **refused** — it declares a retired frontmatter field
|
|
85
|
+
* — and it is an error; a skipped one legitimately belongs to another pass, and
|
|
86
|
+
* there are thousands of those. Folding the first into the second is what let a
|
|
87
|
+
* whole tree be filtered out in silence (#56).
|
|
88
88
|
*
|
|
89
89
|
* @typedef {object} PassStats
|
|
90
90
|
* @property {number} compiled - Notes that became a document.
|
|
91
|
-
* @property {number} skippedDraft - Notes marked `draft: true`.
|
|
92
91
|
* @property {number} skippedNoId - Notes with no `id`, where that is tolerated.
|
|
93
92
|
* @property {number} skippedOther - Notes this pass does not claim.
|
|
94
|
-
* @property {number} declined - Notes refused because they declare
|
|
95
|
-
*
|
|
93
|
+
* @property {number} declined - Notes refused because they declare a retired
|
|
94
|
+
* frontmatter field. Counted as errors, never as skips.
|
|
96
95
|
*/
|
|
97
96
|
|
|
98
97
|
/**
|
|
@@ -336,9 +335,6 @@ export class BasePackCompiler {
|
|
|
336
335
|
const { markdown: tabulated, lineMap } = expandNoteTables(body, {
|
|
337
336
|
docs: this.contentDocs,
|
|
338
337
|
name,
|
|
339
|
-
// The repository's package, not the note's: every note in the tree
|
|
340
|
-
// is this package's note, whether or not it says so (#56).
|
|
341
|
-
pkg: contentPackage(),
|
|
342
338
|
fm,
|
|
343
339
|
bodyLine,
|
|
344
340
|
});
|
|
@@ -512,15 +508,13 @@ export class BasePackCompiler {
|
|
|
512
508
|
if (stats.skippedNoId) {
|
|
513
509
|
log.info(`Skipped ${stats.skippedNoId} note(s) missing id`);
|
|
514
510
|
}
|
|
515
|
-
if (stats.skippedDraft) {
|
|
516
|
-
log.info(`Skipped ${stats.skippedDraft} draft(s)`);
|
|
517
|
-
}
|
|
518
511
|
if (stats.declined) {
|
|
519
512
|
// Its own line, at error level: these are not skips, and burying
|
|
520
513
|
// them in the skipped tally is the defect (#56). Each one has
|
|
521
514
|
// already been named individually as a diagnostic.
|
|
522
515
|
log.error(
|
|
523
|
-
`Declined ${stats.declined} note(s) declaring
|
|
516
|
+
`Declined ${stats.declined} note(s) declaring a retired ` +
|
|
517
|
+
`frontmatter field`,
|
|
524
518
|
);
|
|
525
519
|
}
|
|
526
520
|
this.reportDetail(stats);
|
|
@@ -535,7 +529,6 @@ export class BasePackCompiler {
|
|
|
535
529
|
/** @type {PassStats} */
|
|
536
530
|
const stats = {
|
|
537
531
|
compiled: 0,
|
|
538
|
-
skippedDraft: 0,
|
|
539
532
|
skippedNoId: 0,
|
|
540
533
|
skippedOther: 0,
|
|
541
534
|
declined: 0,
|
|
@@ -560,18 +553,27 @@ export class BasePackCompiler {
|
|
|
560
553
|
stats.skippedOther++;
|
|
561
554
|
continue;
|
|
562
555
|
}
|
|
563
|
-
// The
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
//
|
|
556
|
+
// The retired frontmatter fields, refused before `selects` so a
|
|
557
|
+
// note is answered whichever pass would have claimed it — and
|
|
558
|
+
// whatever the declared value says.
|
|
559
|
+
//
|
|
560
|
+
// - `package:` (#56): a note's package is the repository's
|
|
561
|
+
// configured one, so declaring it restates a constant.
|
|
562
|
+
// - `draft:` (#69): it excluded the note from the packs, the
|
|
563
|
+
// manifest and the site, and no checker reported the links that
|
|
564
|
+
// left dangling.
|
|
565
|
+
//
|
|
566
|
+
// Both are reported and counted — never skipped, which is how a
|
|
567
|
+
// tree naming a package nothing answers to used to compile zero
|
|
568
|
+
// notes and exit 0. The file comes from the diagnostic locator, so
|
|
569
|
+
// neither message may repeat it.
|
|
569
570
|
try {
|
|
570
|
-
|
|
571
|
+
assertNoDeclaredPackage(fm, { absPath });
|
|
572
|
+
assertNoDraftField(fm, { absPath });
|
|
571
573
|
} catch (err) {
|
|
572
574
|
stats.declined++;
|
|
573
575
|
this.errorCount++;
|
|
574
|
-
this.noteError(err.message);
|
|
576
|
+
this.noteError(err.message, err.position);
|
|
575
577
|
continue;
|
|
576
578
|
}
|
|
577
579
|
// Checked before `selects`, and therefore for every note this
|
|
@@ -584,11 +586,6 @@ export class BasePackCompiler {
|
|
|
584
586
|
stats.skippedOther++;
|
|
585
587
|
continue;
|
|
586
588
|
}
|
|
587
|
-
if (fm.draft === true) {
|
|
588
|
-
stats.skippedDraft++;
|
|
589
|
-
log.debug(`Skipping draft: ${absPath}`);
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
589
|
if (!fm.id) {
|
|
593
590
|
if (this.constructor.requiresId) {
|
|
594
591
|
throw new Error(`${Label} missing id: ${absPath}`);
|
|
@@ -597,9 +594,9 @@ export class BasePackCompiler {
|
|
|
597
594
|
this.noteWarn(`${label} note has no id, skipping`);
|
|
598
595
|
continue;
|
|
599
596
|
}
|
|
600
|
-
// Which pack of this type takes it. Applied after the
|
|
601
|
-
//
|
|
602
|
-
//
|
|
597
|
+
// Which pack of this type takes it. Applied after the id check —
|
|
598
|
+
// a note with no id is nobody's document, so its routing is
|
|
599
|
+
// nobody's business — and before `skipNote`, so a note this pack
|
|
603
600
|
// does not own never reaches this pass's own rejection rules.
|
|
604
601
|
try {
|
|
605
602
|
if (!this.routesHere(fm)) {
|
package/engine/content-links.mjs
CHANGED
|
@@ -52,7 +52,8 @@ import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
|
52
52
|
import { expandContentTables } from "./content-tables.mjs";
|
|
53
53
|
import { walkMarkdownTree } from "./helpers.mjs";
|
|
54
54
|
import { hasDocEntry } from "./item-docs.mjs";
|
|
55
|
-
import {
|
|
55
|
+
import { contentPackage } from "./content-package.mjs";
|
|
56
|
+
import { searchableFrontmatter } from "./note-package.mjs";
|
|
56
57
|
import {
|
|
57
58
|
canonicalKey,
|
|
58
59
|
loadForeignManifests,
|
|
@@ -130,12 +131,13 @@ export function buildLinkIndex(
|
|
|
130
131
|
const byAlias = new Map();
|
|
131
132
|
const aliasCollide = new Set();
|
|
132
133
|
|
|
134
|
+
// The one package every note in this tree belongs to. Taken from the
|
|
135
|
+
// configuration, never from a note: `package:` is retired, so there is no
|
|
136
|
+
// second source an address could disagree with (#56).
|
|
137
|
+
const pkg = contentPackage();
|
|
138
|
+
|
|
133
139
|
for (const note of notes) {
|
|
134
140
|
const { fm, type } = note;
|
|
135
|
-
// Derived, never read out of frontmatter: `package:` is optional, and a
|
|
136
|
-
// note that declares nothing addresses exactly as one that declares the
|
|
137
|
-
// configured package (#56).
|
|
138
|
-
const pkg = notePackage(fm);
|
|
139
141
|
if (typeof fm.shortcode === "string" && fm.shortcode) {
|
|
140
142
|
byKey.set(`${type}/${fm.shortcode}`.toLowerCase(), note);
|
|
141
143
|
// The canonical, fully qualified address alongside the short one,
|
|
@@ -170,7 +172,7 @@ export function buildLinkIndex(
|
|
|
170
172
|
// A foreign package may use a type this tree has never seen, so its types
|
|
171
173
|
// join `types` — otherwise `readQualifier` reads the link as prose and it
|
|
172
174
|
// is never checked at all.
|
|
173
|
-
const localPackages = new Set(
|
|
175
|
+
const localPackages = new Set([pkg]);
|
|
174
176
|
const foreign =
|
|
175
177
|
manifestDir ?
|
|
176
178
|
loadForeignManifests(manifestDir, localPackages)
|
|
@@ -178,15 +180,15 @@ export function buildLinkIndex(
|
|
|
178
180
|
for (const v of foreign.index.values()) if (v.type) types.add(v.type);
|
|
179
181
|
|
|
180
182
|
const packages = new Set([
|
|
181
|
-
...
|
|
183
|
+
...(byKey.size ? [pkg] : []),
|
|
182
184
|
...foreign.packages,
|
|
183
185
|
]);
|
|
184
186
|
|
|
185
187
|
/** The searchable universe a `dataview` table draws its rows from. */
|
|
186
188
|
const tableDocs = notes.map((n) => ({
|
|
187
|
-
// Package present
|
|
188
|
-
// {@link searchableFrontmatter} (#56).
|
|
189
|
-
fm: searchableFrontmatter(n.fm),
|
|
189
|
+
// Package present for a `WHERE … package = "…"` clause, synthesised
|
|
190
|
+
// rather than authored — see {@link searchableFrontmatter} (#56).
|
|
191
|
+
fm: searchableFrontmatter(n.fm, pkg),
|
|
190
192
|
path: n.rel,
|
|
191
193
|
tld: n.rel.split("/")[0],
|
|
192
194
|
folder: path.dirname(n.rel).split("/").pop(),
|
|
@@ -205,9 +207,9 @@ export function buildLinkIndex(
|
|
|
205
207
|
let body = note.body;
|
|
206
208
|
if (/^[ \t]*(?:`{3,}|~{3,})[ \t]*dataview\b/im.test(body)) {
|
|
207
209
|
body = expandContentTables(body, {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
// Unfiltered: every note in the tree is this package's, so
|
|
211
|
+
// there is no other package's note to exclude (#56).
|
|
212
|
+
docs: tableDocs,
|
|
211
213
|
linkable: (d) => Boolean(d.fm.shortcode),
|
|
212
214
|
source: note.file,
|
|
213
215
|
}).markdown;
|
|
@@ -38,8 +38,9 @@ import { loadPackConfig } from "./pack-config.mjs";
|
|
|
38
38
|
* `package:` frontmatter and the compilers kept the ones that matched. Every
|
|
39
39
|
* content tree is single-package — each is single-sourced in the repository that
|
|
40
40
|
* ships it — so the field restated this constant once per note while a value
|
|
41
|
-
* that matched nothing filtered the whole tree out in silence.
|
|
42
|
-
*
|
|
41
|
+
* that matched nothing filtered the whole tree out in silence. That field is
|
|
42
|
+
* retired and declaring it now fails the build; this value stays, here, where
|
|
43
|
+
* it is declared once.
|
|
43
44
|
*
|
|
44
45
|
* Stable across compilation targets. If this content were ever compiled for a
|
|
45
46
|
* second game system, it would still be published as `sohl` — only the Foundry
|
|
@@ -147,8 +147,8 @@ function workedExample(type, fields) {
|
|
|
147
147
|
`type: ${type}`,
|
|
148
148
|
"shortcode: xmpl",
|
|
149
149
|
// No `package:`. A note's package is the repository's configured
|
|
150
|
-
// `contentPackage`,
|
|
151
|
-
//
|
|
150
|
+
// `contentPackage`, and declaring the field is a build error (#56) —
|
|
151
|
+
// this example is the smallest note that compiles.
|
|
152
152
|
"id: <16-character id>",
|
|
153
153
|
"sohl:",
|
|
154
154
|
" archetype: null",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
import { authoredFields } from "./field-spec.mjs";
|
|
52
52
|
import { positionInFrontmatter } from "./diagnostics.mjs";
|
|
53
53
|
import { RETIRED_TYPES } from "./ids.mjs";
|
|
54
|
+
import { draftRetiredMessage } from "./retired-fields.mjs";
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* `sohl:` keys every type accepts, whatever its schema says.
|
|
@@ -211,6 +212,31 @@ export function lintNote(note, { schemas, index }) {
|
|
|
211
212
|
const at = (key, literal) =>
|
|
212
213
|
positionInFrontmatter(raw(), key, literal ?? undefined);
|
|
213
214
|
|
|
215
|
+
// The retired top-level fields, checked before the type: a note may carry
|
|
216
|
+
// one whatever its type is, and each finding stands on its own. Reported
|
|
217
|
+
// here as well as refused at compile because this is where an author meets
|
|
218
|
+
// every finding in the tree at once, rather than one note at a time (#56).
|
|
219
|
+
if (Object.hasOwn(fm, "package")) {
|
|
220
|
+
findings.push({
|
|
221
|
+
file: note.file,
|
|
222
|
+
...at("package"),
|
|
223
|
+
severity: "error",
|
|
224
|
+
message:
|
|
225
|
+
"`package:` is a retired frontmatter field — delete it. A " +
|
|
226
|
+
"note's package is this repository's configured " +
|
|
227
|
+
"`contentPackage`, in package-build.config.yaml, and every " +
|
|
228
|
+
"note in the tree belongs to it",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (Object.hasOwn(fm, "draft")) {
|
|
232
|
+
findings.push({
|
|
233
|
+
file: note.file,
|
|
234
|
+
...at("draft"),
|
|
235
|
+
severity: "error",
|
|
236
|
+
message: draftRetiredMessage(),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
214
240
|
const replacement = RETIRED_TYPES[type];
|
|
215
241
|
if (replacement) {
|
|
216
242
|
findings.push({
|
package/engine/helpers.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import log from "loglevel";
|
|
|
36
36
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
37
37
|
import { packRouter } from "./pack-router.mjs";
|
|
38
38
|
import { contentPackage, foundryPackageId } from "./content-package.mjs";
|
|
39
|
-
import {
|
|
39
|
+
import { searchableFrontmatter } from "./note-package.mjs";
|
|
40
40
|
import { loadForeignManifests, PACKAGE_BASE } from "./kb-manifest.mjs";
|
|
41
41
|
import { buildWikilinkIndex, convertWikilinks } from "./wikilinks.mjs";
|
|
42
42
|
import { expandContentTables } from "./content-tables.mjs";
|
|
@@ -602,8 +602,9 @@ export function collectContentDocs(contentBase) {
|
|
|
602
602
|
if (!fm) continue;
|
|
603
603
|
const segments = path.relative(contentBase, absPath).split(path.sep);
|
|
604
604
|
docs.push({
|
|
605
|
-
// With its package
|
|
606
|
-
//
|
|
605
|
+
// With its package supplied for a `WHERE … package = "…"` query —
|
|
606
|
+
// synthesised from the configuration, since no note declares it
|
|
607
|
+
// (#56).
|
|
607
608
|
fm: searchableFrontmatter(fm),
|
|
608
609
|
// POSIX-separated and relative to the content root — what a
|
|
609
610
|
// `path:` search term globs, on every platform.
|
|
@@ -636,17 +637,15 @@ const packLinkable = (doc) =>
|
|
|
636
637
|
* Expand the fenced `dataview` tables in one note's markdown, before wikilinks
|
|
637
638
|
* are resolved — so a generated cell may itself be a wikilink.
|
|
638
639
|
*
|
|
639
|
-
* A table searches
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
* unswept — or every swept — note from the table (#56).
|
|
640
|
+
* A table searches the whole tree, which is one package's notes and nothing
|
|
641
|
+
* else — so there is no longer a package to scope on. It used to filter, back
|
|
642
|
+
* when a tree could hold several packages' notes and `package:` said which was
|
|
643
|
+
* which; that field is retired and the filter with it (#56).
|
|
644
644
|
*
|
|
645
645
|
* @param {string} body - The note's markdown body.
|
|
646
646
|
* @param {object} ctx
|
|
647
647
|
* @param {Array<object>} ctx.docs - From {@link collectContentDocs}.
|
|
648
648
|
* @param {string} ctx.name - The note, for the error message.
|
|
649
|
-
* @param {string} [ctx.pkg] - The source note's package.
|
|
650
649
|
* @param {object} [ctx.fm] - The source note's frontmatter, which is what a
|
|
651
650
|
* query's `this` reads. Its entry in `docs` supplies the path as well.
|
|
652
651
|
* @param {number} [ctx.bodyLine] - 1-based file line of the body's first line,
|
|
@@ -659,8 +658,7 @@ const packLinkable = (doc) =>
|
|
|
659
658
|
* compile rather than shipping a table-shaped hole. The error carries
|
|
660
659
|
* `position`, the directive's own line.
|
|
661
660
|
*/
|
|
662
|
-
export function expandNoteTables(body, { docs, name,
|
|
663
|
-
const scoped = pkg ? docs.filter((d) => notePackage(d.fm) === pkg) : docs;
|
|
661
|
+
export function expandNoteTables(body, { docs, name, fm, bodyLine }) {
|
|
664
662
|
const self =
|
|
665
663
|
fm ?
|
|
666
664
|
(docs.find((d) => d.fm?.id && d.fm.id === fm.id) ?? {
|
|
@@ -668,7 +666,7 @@ export function expandNoteTables(body, { docs, name, pkg, fm, bodyLine }) {
|
|
|
668
666
|
})
|
|
669
667
|
: undefined;
|
|
670
668
|
const { markdown, errors, lineMap } = expandContentTables(body ?? "", {
|
|
671
|
-
docs
|
|
669
|
+
docs,
|
|
672
670
|
linkable: packLinkable,
|
|
673
671
|
source: name,
|
|
674
672
|
self,
|
package/engine/index.mjs
CHANGED
|
@@ -54,9 +54,12 @@ export * as packRouter from "./pack-router.mjs";
|
|
|
54
54
|
/** The content package a build compiles, and the Foundry package it ships in. */
|
|
55
55
|
export * as contentPackage from "./content-package.mjs";
|
|
56
56
|
|
|
57
|
-
/** Which content package a note belongs to, and refusing one that
|
|
57
|
+
/** Which content package a note belongs to, and refusing one that says so. */
|
|
58
58
|
export * as notePackage from "./note-package.mjs";
|
|
59
59
|
|
|
60
|
+
/** Frontmatter fields a note may no longer declare, and the refusal of them. */
|
|
61
|
+
export * as retiredFields from "./retired-fields.mjs";
|
|
62
|
+
|
|
60
63
|
/** The shipped Foundry manifest: locating it, reading it, guarding its id. */
|
|
61
64
|
|
|
62
65
|
/** The URL a content note is published at — the one web-address rule. */
|