@heroiclands/package-build 20.4.0 → 20.6.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 +288 -0
- package/CONTENT.md +213 -20
- package/README.md +19 -1
- package/bin/content-build.mjs +135 -32
- package/bin/package-build.mjs +46 -13
- package/content-config.mjs +345 -101
- package/docs/api.md +1352 -0
- package/docs/commands.md +1609 -0
- package/docs/configuration.md +1432 -0
- package/docs/content-format.md +16 -6
- package/docs/diagnostics.md +356 -0
- package/docs/getting-started.md +813 -0
- package/docs/project-setup.md +469 -0
- package/engine/actor-compiler.mjs +30 -27
- package/engine/address-diff.mjs +45 -41
- package/engine/base-compiler.mjs +6 -0
- package/engine/bundles.mjs +9 -0
- package/engine/content-address.mjs +9 -9
- package/engine/content-index.mjs +44 -23
- package/engine/content-links.mjs +44 -11
- package/engine/content-lint.mjs +44 -10
- package/engine/content-tables.mjs +32 -27
- package/engine/folder-notes.mjs +4 -2
- package/engine/frontmatter-lint.mjs +35 -38
- package/engine/generate.mjs +5 -0
- package/engine/helpers.mjs +86 -32
- package/engine/index.mjs +12 -2
- package/engine/journals.mjs +9 -0
- package/engine/note-claims.mjs +18 -10
- package/engine/note-schemas.mjs +0 -5
- package/engine/note-vocabulary.mjs +32 -31
- package/engine/pack-config.mjs +26 -12
- package/engine/pack-router.mjs +0 -0
- package/engine/pdf-build.mjs +464 -0
- package/engine/pdf-fonts.mjs +420 -0
- package/engine/pdf-render.mjs +876 -0
- package/engine/pdf-toc.mjs +525 -0
- package/engine/scenes.mjs +14 -5
- package/engine/schema-check.mjs +1 -1
- package/engine/site-build.mjs +21 -3
- package/engine/web-wikilinks.mjs +6 -3
- package/engine/wikilinks.mjs +2 -4
- package/hm3/actors.mjs +8 -0
- package/hm3/items.mjs +8 -0
- package/package.json +1 -1
- package/release.mjs +63 -3
- package/sohl/actors.mjs +8 -0
- package/sohl/items.mjs +8 -0
- package/sohl/note-schemas.mjs +5 -5
- package/types/content-config.d.mts +66 -15
- package/types/engine/actor-compiler.d.mts +34 -30
- package/types/engine/address-diff.d.mts +57 -3
- package/types/engine/base-compiler.d.mts +10 -2
- package/types/engine/bundles.d.mts +9 -0
- package/types/engine/content-address.d.mts +9 -9
- package/types/engine/content-index.d.mts +57 -13
- package/types/engine/content-lint.d.mts +6 -4
- package/types/engine/content-tables.d.mts +49 -18
- package/types/engine/frontmatter-lint.d.mts +3 -2
- package/types/engine/helpers.d.mts +105 -31
- package/types/engine/index.d.mts +4 -0
- package/types/engine/journals.d.mts +9 -0
- package/types/engine/note-claims.d.mts +17 -10
- package/types/engine/note-vocabulary.d.mts +23 -196
- package/types/engine/pack-config.d.mts +4 -4
- package/types/engine/pdf-build.d.mts +42 -0
- package/types/engine/pdf-fonts.d.mts +30 -0
- package/types/engine/pdf-render.d.mts +156 -0
- package/types/engine/pdf-toc.d.mts +114 -0
- package/types/engine/scenes.d.mts +10 -1
- package/types/engine/schema-check.d.mts +2 -2
- package/types/engine/site-build.d.mts +34 -6
- package/types/engine/wikilinks.d.mts +2 -3
- package/types/hm3/actors.d.mts +8 -0
- package/types/hm3/items.d.mts +8 -0
- package/types/release.d.mts +15 -4
- package/types/sohl/actors.d.mts +10 -2
- package/types/sohl/items.d.mts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,293 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 20.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a2742cc: **PDF book outline** — a book's printed table of contents now lists only the
|
|
8
|
+
sections its document tree declares; every note stays reachable from the PDF
|
|
9
|
+
bookmarks panel, titled from its own name, without also cluttering the printed
|
|
10
|
+
contents. A note's own body headings never surface on either surface, so a note
|
|
11
|
+
opening with a heading that repeats its own title no longer shows a duplicate
|
|
12
|
+
entry in the sidebar. Every heading — printed, bookmarked or neither — is still
|
|
13
|
+
a working link target, including one an author never gave an explicit anchor.
|
|
14
|
+
|
|
15
|
+
## 20.5.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- cbe3266: **A reference for every subpath entry this package exports**
|
|
20
|
+
|
|
21
|
+
`docs/api.md` documents the whole programmatic surface — `engine`, `sohl`
|
|
22
|
+
and `hm3` included, on the same footing as the packaging half — organized by
|
|
23
|
+
how a consumer imports it: signature, what it returns, and when to reach
|
|
24
|
+
for it, with a runnable example per subpath. Most of it is pure — source
|
|
25
|
+
text or already-loaded data in, findings or values out, leaving discovery,
|
|
26
|
+
I/O and reporting to the caller — and the reference names the handful of
|
|
27
|
+
packaging functions that necessarily touch the filesystem or a subprocess.
|
|
28
|
+
- 8e2b5d3: **A key-by-key reference for `package-build.config.yaml`**
|
|
29
|
+
|
|
30
|
+
`docs/configuration.md` documents every top-level key, every nested key, its
|
|
31
|
+
type, whether it is required, its default, and the exact message an author
|
|
32
|
+
sees when a value is wrong — quoted from the validator, so the error a build
|
|
33
|
+
prints is searchable against the page that explains it.
|
|
34
|
+
|
|
35
|
+
It also documents the values a repository never writes: `rootDir` and
|
|
36
|
+
`foundryPackage` are derived from where the file sits and from the adjacent
|
|
37
|
+
`package.json`; `stats.systemVersion` is derived from `package.json` or from
|
|
38
|
+
`systems:`; `itemBuilders` accepts a registry name (`sohl`, `hm3`) in YAML,
|
|
39
|
+
resolved before validation. Authoring the first three directly is refused,
|
|
40
|
+
with the message that says so.
|
|
41
|
+
|
|
42
|
+
`packageBuild:` — the section `@heroiclands/package-build`'s own packaging
|
|
43
|
+
half reads — gets the same treatment: staging assets, the manifest
|
|
44
|
+
pass-through and the keys it derives and refuses to have overwritten, the
|
|
45
|
+
localization coverage settings, the container and end-to-end test
|
|
46
|
+
configuration.
|
|
47
|
+
- cc3d90b: **The content tree publishes as a book**
|
|
48
|
+
|
|
49
|
+
A third surface beside the compendium packs and the website: one searchable,
|
|
50
|
+
bookmarked PDF, built by `content-build pdf` and attached to a release by
|
|
51
|
+
`package-build release`.
|
|
52
|
+
|
|
53
|
+
**A book is a selection, not a rendering of everything.** The packs and the site
|
|
54
|
+
publish the whole tree; a book is a declared structure whose leaves pick notes
|
|
55
|
+
out of the corpus with a `WHERE` clause, interleaved with prose that need not be
|
|
56
|
+
in the tree at all. `pdf.document` names that structure.
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
pdf:
|
|
60
|
+
title: The Hârn Ensemble
|
|
61
|
+
document: book.yaml
|
|
62
|
+
fonts:
|
|
63
|
+
serif: Libertinus Serif
|
|
64
|
+
mono: DejaVu Sans Mono
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**`publish.site` decides whether one is built, and it is the only switch.**
|
|
68
|
+
`content` builds a book; `homepage` does not — the same fence that stops the
|
|
69
|
+
tree being walked for pages stops it being walked for a book, so the four
|
|
70
|
+
packages that publish only a homepage cannot start emitting a content document
|
|
71
|
+
because a `pdf:` block appeared. A package with no block, no tree, or no
|
|
72
|
+
compiler builds nothing, says why, and exits 0.
|
|
73
|
+
|
|
74
|
+
| Property | How it is got |
|
|
75
|
+
| ----------------------- | ------------------------------------------------------------------- |
|
|
76
|
+
| Searchable | Real text; every embedded font carries a `ToUnicode` map. |
|
|
77
|
+
| Bookmark outline | Every section and entry is a heading, so the sidebar is the way in. |
|
|
78
|
+
| Page-numbered contents | Shallower than the bookmarks — 2,500 entries would be 40 pages. |
|
|
79
|
+
| Repeating table headers | A property table spilling a page keeps its column names. |
|
|
80
|
+
| Internal references | A wikilink between two notes of the book resolves inside the PDF. |
|
|
81
|
+
| External references | A cross-package link, or a note not selected, stays a URL. |
|
|
82
|
+
|
|
83
|
+
**Typst is a binary, not a dependency.** It is found on `PATH` or named by
|
|
84
|
+
`pdf.binary`. Bundling a native compiler would put a platform-specific artefact
|
|
85
|
+
in the dependency tree of three repositories, only one of which is mostly a
|
|
86
|
+
book. A missing binary is a finding, and the `.typ` source is written anyway.
|
|
87
|
+
|
|
88
|
+
Proved against `harn-ensemble`: 2,517 entries, 1,270 pages, 10,421 outline
|
|
89
|
+
nodes, 34 seconds, no findings.
|
|
90
|
+
|
|
91
|
+
Additive — a package that configures no `pdf:` block is unaffected.
|
|
92
|
+
- c9dfa50: **A `doc` has two more genres to choose from**
|
|
93
|
+
|
|
94
|
+
`subType: howto` and `subType: concept` join `rules`, `userguide` and
|
|
95
|
+
`reference`:
|
|
96
|
+
|
|
97
|
+
- **`howto`** — a task with an outcome, written as the steps that reach it.
|
|
98
|
+
- **`concept`** — an explanation of how something works and why it is shaped
|
|
99
|
+
that way, read to understand rather than to follow.
|
|
100
|
+
|
|
101
|
+
Between them they cover the prose a package writes about itself, which had no
|
|
102
|
+
genre to declare and was left to a directory name to imply.
|
|
103
|
+
|
|
104
|
+
`subType` stays _a genre and only a genre_: it says what kind of page this is,
|
|
105
|
+
never who reads it. A page written for a developer is a `howto` or a `concept`
|
|
106
|
+
like any other, and the audience is the section it sits in.
|
|
107
|
+
|
|
108
|
+
Both spellings are one word, as `userguide` is: a `subType` is held to the
|
|
109
|
+
address charset, so `how-to` is refused for its hyphen.
|
|
110
|
+
- 00ce257: **A package that publishes documentation**
|
|
111
|
+
|
|
112
|
+
`packageKind` takes a third value, `documentation`: a package that publishes a
|
|
113
|
+
website and the book built from the same notes, and compiles no Foundry
|
|
114
|
+
documents at all. `systems` and `modules` say where Foundry installs a package;
|
|
115
|
+
`documentation` says it installs nowhere.
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
contentPackage: toolkit
|
|
119
|
+
packageKind: documentation
|
|
120
|
+
|
|
121
|
+
publish:
|
|
122
|
+
site: content
|
|
123
|
+
address: { prefix: guide/ }
|
|
124
|
+
|
|
125
|
+
site:
|
|
126
|
+
out: site/content
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`publish` is required, and `site: content` — publishing the tree is the whole of
|
|
130
|
+
what the kind does, and `content` is also what builds the book.
|
|
131
|
+
|
|
132
|
+
**What it refuses, by name and with a locator.** `packs`, `itemBuilders`,
|
|
133
|
+
`docs`, `compatibility`, `relationships`, `systems`, `requiresSystem`, `stats`
|
|
134
|
+
and `foundryPackage` each describe a Foundry package, so each fails at load
|
|
135
|
+
naming the key and the line and column it was written on, rather than being read
|
|
136
|
+
and ignored. `packs` is otherwise still required to declare at least one pack.
|
|
137
|
+
|
|
138
|
+
**What follows from the kind.**
|
|
139
|
+
|
|
140
|
+
- _No manifest, and no compile._ `package-build manifest` refuses rather than
|
|
141
|
+
writing a `module.json` for a package Foundry never installs. The compile
|
|
142
|
+
passes refuse too, rather than exiting 0 having compiled nothing.
|
|
143
|
+
- _No Foundry ids to author._ A note's id is derived from its address, and no
|
|
144
|
+
pass here asks for a pinned one.
|
|
145
|
+
- _`doc` and `homepage` are the whole vocabulary._ Every other note type exists
|
|
146
|
+
to become a Foundry document, so a note carrying one has no destination;
|
|
147
|
+
`content-build lint` reports it at its `type:` line.
|
|
148
|
+
- _No asset root._ `assetRoot` and `foundryPackage` resolve to nothing, so a
|
|
149
|
+
note's `img:` names the owning package (`systems/…`, `modules/…`) or a URL.
|
|
150
|
+
A path this package would have to serve itself is refused.
|
|
151
|
+
- _A content index all the same._ `content-build content-index` emits
|
|
152
|
+
`<contentPackage>-metadata.jsonl` as it does everywhere else, so another
|
|
153
|
+
package can resolve an address into this one.
|
|
154
|
+
|
|
155
|
+
Systems and modules are unchanged: the asset root, the `_stats` stamp and the
|
|
156
|
+
pack requirement all behave exactly as before.
|
|
157
|
+
- 46d6de3: **Getting started** — `docs/getting-started.md` walks an empty directory to a
|
|
158
|
+
package that builds: identity, configuration, a first note, the checks, the
|
|
159
|
+
compiled packs, the manifest, the content index and the release archive. Each
|
|
160
|
+
step says what it produces and how to tell it worked, with the output it
|
|
161
|
+
actually prints. It was written by walking it.
|
|
162
|
+
|
|
163
|
+
**Project setup** — `docs/project-setup.md` covers what a repository carries
|
|
164
|
+
beyond the build configuration: `package.json` and what each script in the chain
|
|
165
|
+
is _for_, the shared Prettier re-export and what `.prettierignore` is really
|
|
166
|
+
guarding, the packaged git hooks and their per-hook switches, `.changeset/` and
|
|
167
|
+
the four settings that are decisions rather than preferences, the label registry
|
|
168
|
+
pair, and the directory layout — what the build reads, and everything it writes
|
|
169
|
+
under `build/`.
|
|
170
|
+
- f9e0e27: **`content-build pdf --version` is now `--book-version`.**
|
|
171
|
+
|
|
172
|
+
`--version` collided with yargs' own reserved top-level `--version`, so the
|
|
173
|
+
option could not actually take a value — passing one failed with
|
|
174
|
+
`Unknown argument`. `--book-version` stamps the title page and the file name
|
|
175
|
+
exactly as before; the plain `content-build --version` answers this package's
|
|
176
|
+
own version, unaffected.
|
|
177
|
+
|
|
178
|
+
**A thrown error from `content-build pdf` no longer crashes with a
|
|
179
|
+
`ReferenceError`.** It now reports the same located diagnostic and non-zero
|
|
180
|
+
exit every other command's catch block produces.
|
|
181
|
+
- a6c77d2: **A guide to reading this toolchain's diagnostics**
|
|
182
|
+
|
|
183
|
+
`docs/diagnostics.md` explains the `file:line:column: severity: message` form
|
|
184
|
+
every warning and error carries: why a field is dropped rather than guessed,
|
|
185
|
+
why both severities print to stderr, how a configuration error is located,
|
|
186
|
+
and which commands fail a run on an error-severity finding. It closes with a
|
|
187
|
+
runnable example that parses a command's output programmatically.
|
|
188
|
+
- 51e334a: **A full command reference**
|
|
189
|
+
|
|
190
|
+
`docs/commands.md` documents every command both binaries expose — `package-build`
|
|
191
|
+
and `content-build`, 25 commands between them — with what each reads, what it
|
|
192
|
+
writes, its options and their defaults, its exit codes, and a worked example.
|
|
193
|
+
|
|
194
|
+
Several corners never had a home before this: `content-build pdf`, and the
|
|
195
|
+
options `--coverage`, `--doc`, `--fields`, `--id`, `--references`, `--registry`
|
|
196
|
+
and `--root`.
|
|
197
|
+
|
|
198
|
+
The document is checked against the actual `yargs` definitions in both
|
|
199
|
+
binaries, so an option or an action added to either one and left undocumented
|
|
200
|
+
fails the build.
|
|
201
|
+
|
|
202
|
+
### Patch Changes
|
|
203
|
+
|
|
204
|
+
- 6e80fbd: **`content-config.mjs`'s own documentation now matches what `defineConfig` accepts**
|
|
205
|
+
|
|
206
|
+
The worked example at the top of `content-config.mjs` — and its `.mjs` twin in
|
|
207
|
+
`CONTENT.md` — authored `stats.systemId`, a key `defineConfig` refuses as
|
|
208
|
+
derived. Both now carry only what loads.
|
|
209
|
+
|
|
210
|
+
The `contentPackage` refusal, and the sibling refusal for a section's
|
|
211
|
+
`listType` / `listSubType`, named the wrong character class — `[A-Za-z0-9]`
|
|
212
|
+
when the enforced charset is lowercase only. Both now say `lowercase
|
|
213
|
+
alphanumeric` and print the pattern actually enforced, so an author who writes
|
|
214
|
+
`contentPackage: PackageBuild` is told what is wrong with it rather than sent
|
|
215
|
+
looking for a character they do not have.
|
|
216
|
+
- bd6c20b: **`docs/configuration.md` now matches `packageKind: documentation`**
|
|
217
|
+
|
|
218
|
+
The configuration reference names all three `packageKind` values —
|
|
219
|
+
`systems`, `modules` and `documentation` — in the summary table and in the
|
|
220
|
+
`packageKind` section itself, and states which keys a `documentation`
|
|
221
|
+
package requires, which it refuses (quoting each located refusal message
|
|
222
|
+
verbatim), and how `foundryPackage`, `assetRoot` and `stats` resolve for it.
|
|
223
|
+
- fb04049: **`content-build docs item-fields --check` reports a stale page in the located form**
|
|
224
|
+
|
|
225
|
+
The stale-page diagnostic now starts with the file's path, unprefixed by a
|
|
226
|
+
timestamp — matching every other located failure this command line emits, and
|
|
227
|
+
readable by the same tools that already parse the rest of them.
|
|
228
|
+
- 46b39ef: **The published types match the code they are generated from**
|
|
229
|
+
|
|
230
|
+
The `.d.mts` files ship with `@heroiclands/package-build` and are generated
|
|
231
|
+
from the JSDoc, so a block that is not the one belonging to a symbol ships as
|
|
232
|
+
that symbol's type. `packRelease` now declares the `pdf` option it accepts and
|
|
233
|
+
the `pdf`, `pdfFindings` and `pdfSkipped` fields it returns; `buildSite` and
|
|
234
|
+
`expandNoteTables` declare `sqlTables`; a compiler's `resolveEmbedded`
|
|
235
|
+
declares `modelPackage`; and `expandContentTables` declares the `warnings` it
|
|
236
|
+
returns and the `column` on every error entry. Type-checking the whole
|
|
237
|
+
published declaration surface is clean.
|
|
238
|
+
|
|
239
|
+
**Every exported symbol carries documentation of its own**
|
|
240
|
+
|
|
241
|
+
`NOTE_VOCABULARY`, `DECLARED_TAGS`, `buildIndexRecord`, `collectContentIndex`,
|
|
242
|
+
`expandContentTables`, `walkMarkdownTree`, `declaredPredecessors` and the seven
|
|
243
|
+
pack compiler classes each describe what they take and what they return, so
|
|
244
|
+
hovering one in an editor answers the question asked of it.
|
|
245
|
+
- de54d45: `docs/commands.md` reads as a manual page. Every command carries the same
|
|
246
|
+
labelled sections — **NAME**, **SYNOPSIS**, **DESCRIPTION**, **OPTIONS**,
|
|
247
|
+
**EXIT STATUS**, **EXAMPLES**, **SEE ALSO** — in the same order, so jumping to
|
|
248
|
+
the right command means jumping to the right label. **SEE ALSO** cross-links
|
|
249
|
+
the commands that answer a related question, and every command that emits
|
|
250
|
+
findings or reads a configuration key now points at _Diagnostics_ or
|
|
251
|
+
_Configuration_ directly.
|
|
252
|
+
|
|
253
|
+
`package-build lang check` / `coverage` / `hardcoded` and
|
|
254
|
+
`content-build content-format schema` / `fields` / `notes` each get their own
|
|
255
|
+
section, rather than sharing one.
|
|
256
|
+
- 40fcc1a: **A frontmatter reference resolves in any package the tree can reach.**
|
|
257
|
+
|
|
258
|
+
A `ref:` field — `parentSkillCode`, `assocSkillCode`, `assocAffiliationCode` —
|
|
259
|
+
holds a shortcode, not an address. The system persists it as written and looks
|
|
260
|
+
it up at runtime among the items embedded on one actor, and an actor assembled
|
|
261
|
+
from several packages carries their items side by side. So the reference check
|
|
262
|
+
now asks only whether _any_ reachable package declares the `type`/`shortcode`
|
|
263
|
+
pair: local notes first, then the fetched dependency indexes, with package and
|
|
264
|
+
system wildcarded.
|
|
265
|
+
|
|
266
|
+
This is the rule for a reference alone. A wikilink is unchanged — its target is
|
|
267
|
+
a document to point at, so an omitted package still means this one, and reaching
|
|
268
|
+
another package still requires the fully qualified form.
|
|
269
|
+
|
|
270
|
+
A tree whose references name a parent in a dependency saw every one of them
|
|
271
|
+
reported:
|
|
272
|
+
|
|
273
|
+
| note | `sohl.parentSkillCode` | before | after |
|
|
274
|
+
| ----------------------- | ---------------------- | ------------ | -------------------------- |
|
|
275
|
+
| a language skill | `lang` | no such note | resolves in the dependency |
|
|
276
|
+
| a spirit specialisation | `spirit` | no such note | resolves in the dependency |
|
|
277
|
+
|
|
278
|
+
A reference naming a shortcode no package declares is still an error, and the
|
|
279
|
+
value a note writes is still the value its document carries.
|
|
280
|
+
|
|
281
|
+
`buildLinkIndex` gains `referenceHit(target)`, which is what performs this
|
|
282
|
+
lookup.
|
|
283
|
+
- a331709: **The address-segment charset is documented as lowercase, matching what it enforces.**
|
|
284
|
+
|
|
285
|
+
Comments, JSDoc and the reference documentation described `contentPackage`,
|
|
286
|
+
`type`, `subType` and `shortcode` as `^[A-Za-z0-9]+$` or plainly "alphanumeric",
|
|
287
|
+
which reads as case-insensitive. The charset every one of them is held to is
|
|
288
|
+
lowercase-only, and the wording now says so — in prose and in the refusal
|
|
289
|
+
messages an author hits when a value breaks it.
|
|
290
|
+
|
|
3
291
|
## 20.4.0
|
|
4
292
|
|
|
5
293
|
### Minor Changes
|
package/CONTENT.md
CHANGED
|
@@ -25,8 +25,10 @@ A consuming repository declares one `package-build.config.yaml` at its root:
|
|
|
25
25
|
# every canonical address, the name of the content index it emits, and the
|
|
26
26
|
# package a cross-package wikilink writes to reach one of its notes.
|
|
27
27
|
contentPackage: thalorna
|
|
28
|
-
#
|
|
29
|
-
# asset root a note's `img:` resolves
|
|
28
|
+
# What kind of package this is. "systems" and "modules" are where Foundry
|
|
29
|
+
# installs it, and each decides the served asset root a note's `img:` resolves
|
|
30
|
+
# to — `modules/sohl-thalorna/assets/…`. "documentation" is a package Foundry
|
|
31
|
+
# never installs; see "A package that compiles nothing" below.
|
|
30
32
|
packageKind: modules
|
|
31
33
|
|
|
32
34
|
# The Foundry core range this package supports. `minimum` is stamped into every
|
|
@@ -161,13 +163,13 @@ packageBuild:
|
|
|
161
163
|
assets:
|
|
162
164
|
- { from: assets/icons, to: assets/icons }
|
|
163
165
|
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
166
|
+
# How much of the package reaches the web, plus the address scheme both
|
|
167
|
+
# `manifest` and `site` derive addresses under. `site` is a mode, not a
|
|
168
|
+
# boolean: `homepage` (the default) publishes the authored homepage and no
|
|
169
|
+
# other page; `content` publishes it plus every page the tree compiles to, and
|
|
170
|
+
# is also what builds the book.
|
|
168
171
|
publish:
|
|
169
172
|
site: content
|
|
170
|
-
manifests: { publish: true, consume: true }
|
|
171
173
|
address:
|
|
172
174
|
prefix: kb/
|
|
173
175
|
|
|
@@ -182,8 +184,8 @@ site:
|
|
|
182
184
|
|
|
183
185
|
The loader validates the document, resolves every path against the directory
|
|
184
186
|
the file sits in, fills the optional halves with their defaults
|
|
185
|
-
(`skipDirectories: []`, `packageBuild: {}`, the conventional `paths
|
|
186
|
-
|
|
187
|
+
(`skipDirectories: []`, `packageBuild: {}`, the conventional `paths` and
|
|
188
|
+
`publish.site` at its `homepage` floor),
|
|
187
189
|
derives `assetRoot`, `packDirectories`, `itemTypes` and `docEntryTypes`, and
|
|
188
190
|
freezes the result. A malformed configuration throws a `TypeError` naming the
|
|
189
191
|
offending field — and the line and column it was written on, in the
|
|
@@ -212,6 +214,69 @@ never existed. A module declaring no usable system relationship fails the build
|
|
|
212
214
|
rather than guessing — a wrong `_stats.systemVersion` is invisible until
|
|
213
215
|
something migrates on it.
|
|
214
216
|
|
|
217
|
+
### A package that compiles nothing
|
|
218
|
+
|
|
219
|
+
`packageKind: documentation` is the answer "not a Foundry package at all". It
|
|
220
|
+
publishes a website and the book built from the same notes, and compiles no
|
|
221
|
+
compendium: nothing is installed into a Foundry data directory, so there is no
|
|
222
|
+
manifest, no pack and no document.
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
contentPackage: toolkit
|
|
226
|
+
packageKind: documentation
|
|
227
|
+
|
|
228
|
+
# Required, and `content`: publishing the tree is the whole of what this kind
|
|
229
|
+
# does. `homepage` would leave one authored page, no book and nothing compiled.
|
|
230
|
+
publish:
|
|
231
|
+
site: content
|
|
232
|
+
address: { prefix: guide/ }
|
|
233
|
+
|
|
234
|
+
site:
|
|
235
|
+
out: site/content
|
|
236
|
+
|
|
237
|
+
pdf:
|
|
238
|
+
title: The Toolkit
|
|
239
|
+
document: book.yaml
|
|
240
|
+
fonts: { serif: Libertinus Serif }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`contentPackage`, `paths`, `skipDirectories`, `icons`, `site`, `pdf` and
|
|
244
|
+
`packageBuild` mean exactly what they mean anywhere else. **Every key that
|
|
245
|
+
describes a Foundry package is refused**, by name, with the line and column it
|
|
246
|
+
was written on — `packs`, `itemBuilders`, `docs`, `compatibility`,
|
|
247
|
+
`relationships`, `systems`, `requiresSystem`, `stats` and `foundryPackage`:
|
|
248
|
+
|
|
249
|
+
```text
|
|
250
|
+
package-build.config.yaml:7:1: error: package-build config: `packs` is refused in a `documentation` package, which compiles no compendium, so there are no packs to declare.
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
A key that cannot mean anything in this shape fails at load rather than being
|
|
254
|
+
read and ignored, which is the difference between a configuration that is wrong
|
|
255
|
+
and a build that is quietly wrong.
|
|
256
|
+
|
|
257
|
+
What follows from the kind:
|
|
258
|
+
|
|
259
|
+
- **`foundryPackage` is not derived.** For every other kind the loader reads it
|
|
260
|
+
from the adjacent `package.json`; there is no Foundry package here to carry an
|
|
261
|
+
id, so `foundryPackage` and `assetRoot` are both `null`. A note's `img:`
|
|
262
|
+
therefore names the owning package (`systems/…`, `modules/…`) or a URL — a
|
|
263
|
+
path this package would have to serve itself is refused, because Foundry
|
|
264
|
+
serves no files for a package it does not install.
|
|
265
|
+
- **`package-build manifest` refuses**, rather than emitting a `module.json`
|
|
266
|
+
advertising an installable package with no id, no packs and no compatibility
|
|
267
|
+
range. So does `content-build package compile`, which would otherwise exit 0
|
|
268
|
+
having compiled nothing.
|
|
269
|
+
- **Notes need no `id:`.** An id is derived from a note's address, and a
|
|
270
|
+
hand-assigned one is only ever a pin for a document that already shipped —
|
|
271
|
+
which needs a compendium, and there is none.
|
|
272
|
+
- **The note vocabulary is `doc` and `homepage`.** Every other type exists to
|
|
273
|
+
become a Foundry document, so a note carrying one has no destination;
|
|
274
|
+
`content-build lint` reports it at its `type:` line.
|
|
275
|
+
- **A content index is still published.** `content-build content-index` emits
|
|
276
|
+
`<contentPackage>-metadata.jsonl` exactly as it does elsewhere, so another
|
|
277
|
+
package can resolve an address into this one and a wikilink from its notes can
|
|
278
|
+
reach a page here.
|
|
279
|
+
|
|
215
280
|
### A note's package is the repository's, not the note's
|
|
216
281
|
|
|
217
282
|
`contentPackage` is the **address namespace** every note in the tree is
|
|
@@ -240,7 +305,7 @@ package's id to _be_ its system id.
|
|
|
240
305
|
Because it is a segment of an address, the value is **validated** rather than
|
|
241
306
|
taken as written, and a violation fails the build naming the line it is on:
|
|
242
307
|
|
|
243
|
-
- **
|
|
308
|
+
- **Lowercase alphanumeric** (`^[a-z0-9]+$`). An address is read by counting
|
|
244
309
|
hyphen-separated segments, so the hyphen has to be purely a separator — which
|
|
245
310
|
is why `harn-adventures` is configured as `harnadventures`. This is the same
|
|
246
311
|
rule `shortcode` is already held to, and the two are one constant.
|
|
@@ -257,7 +322,7 @@ taken as written, and a violation fails the build naming the line it is on:
|
|
|
257
322
|
refused.
|
|
258
323
|
|
|
259
324
|
```text
|
|
260
|
-
package-build.config.yaml:1:1: error: package-build config: `contentPackage` is `harn-adventures`, which is not alphanumeric. It is the first segment of every address this package publishes (`harn-adventures-<system>-<type>-<shortcode>`), and an address is read by counting hyphen-separated segments — so anything outside
|
|
325
|
+
package-build.config.yaml:1:1: error: package-build config: `contentPackage` is `harn-adventures`, which is not lowercase alphanumeric (^[a-z0-9]+$). It is the first segment of every address this package publishes (`harn-adventures-<system>-<type>-<shortcode>`), and an address is read by counting hyphen-separated segments — so anything outside that here makes those addresses unreadable rather than merely ugly. `harn-adventures` became `harnadventures`.
|
|
261
326
|
```
|
|
262
327
|
|
|
263
328
|
**`package:` in a note's frontmatter is retired, and declaring it fails the
|
|
@@ -312,7 +377,7 @@ export default defineConfig({
|
|
|
312
377
|
foundryPackage: "sohl-kethira-basic",
|
|
313
378
|
packageKind: "modules",
|
|
314
379
|
compatibility: { minimum: "14.359", verified: "14.364" },
|
|
315
|
-
stats: {
|
|
380
|
+
stats: { lastModifiedBy: "…" },
|
|
316
381
|
itemBuilders: ITEM_BUILDERS,
|
|
317
382
|
packs: [{ name: "items", type: "Item" }],
|
|
318
383
|
});
|
|
@@ -658,7 +723,9 @@ and there are exactly three answers:
|
|
|
658
723
|
|
|
659
724
|
`assetRoot` is derived, never authored: it is
|
|
660
725
|
`<packageKind>/<foundryPackage>/assets`, and it is the one place `systems/sohl`
|
|
661
|
-
(or `modules/sohl-thalorna`) is ever spelled.
|
|
726
|
+
(or `modules/sohl-thalorna`) is ever spelled. The derivation is **conditional on
|
|
727
|
+
the kind** — a `documentation` package has no asset root, and the third row is
|
|
728
|
+
refused there rather than rooted. So `icons/relic.svg` in a module's
|
|
662
729
|
registry compiles to `modules/sohl-relics/assets/icons/relic.svg`, and the same
|
|
663
730
|
string in the system's compiles to `systems/sohl/assets/icons/relic.svg`. An
|
|
664
731
|
already-served `systems/sohl/assets/icons/…` passes through untouched — which is
|
|
@@ -977,9 +1044,10 @@ npx content-build lint some/tree # or a tree named outright
|
|
|
977
1044
|
Checks the three rules every note's **identity** is authored against, and reports
|
|
978
1045
|
each finding in the located form below:
|
|
979
1046
|
|
|
980
|
-
- **Shape** — a `shortcode` is strictly ASCII-alphanumeric. It is
|
|
981
|
-
key referenced from saved world data, and half of the
|
|
982
|
-
address, whose parse needs the separating hyphen to be the
|
|
1047
|
+
- **Shape** — a `shortcode` is strictly lowercase ASCII-alphanumeric. It is
|
|
1048
|
+
the identity key referenced from saved world data, and half of the
|
|
1049
|
+
`type-shortcode` address, whose parse needs the separating hyphen to be the
|
|
1050
|
+
only hyphen.
|
|
983
1051
|
- **Uniqueness** — `(type, shortcode)` names one note. A document is addressed
|
|
984
1052
|
across _every_ pack of its document type, so routing two same-address notes to
|
|
985
1053
|
different packs with `pack:` does not separate them.
|
|
@@ -1149,7 +1217,7 @@ values. A `weapon` declares none — SoHL distinguishes a weapon's uses by strik
|
|
|
1149
1217
|
mode rather than by kind — so `subType` on one is a finding; a `skill` declares
|
|
1150
1218
|
ten, so `subType: crafte` is a finding naming `craft`.
|
|
1151
1219
|
|
|
1152
|
-
**A `type` and a `subType` are both held to `^[
|
|
1220
|
+
**A `type` and a `subType` are both held to `^[a-z0-9]+$`** — the same
|
|
1153
1221
|
constant a `shortcode` is held to, read rather than restated. A type is a
|
|
1154
1222
|
segment of every address — the first of the short form an author writes, the
|
|
1155
1223
|
third of the canonical `package-system-type-shortcode` — so a hyphen in one is
|
|
@@ -1160,7 +1228,7 @@ rule is checked ahead of the closed-set check, which is what makes it reach a
|
|
|
1160
1228
|
type whose values are declared but not yet enumerated:
|
|
1161
1229
|
|
|
1162
1230
|
```text
|
|
1163
|
-
assets/content/Beings/Folk.md:3:1: error: `subType` "common-folk" is not a well-formed subType — a subType is letters and digits only (^[
|
|
1231
|
+
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-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. …
|
|
1164
1232
|
```
|
|
1165
1233
|
|
|
1166
1234
|
One declared value broke that rule: a `doc`'s `user-guide`, now **`userguide`**.
|
|
@@ -1221,7 +1289,7 @@ that harmless: three segments name a _system_ whatever the first segment could
|
|
|
1221
1289
|
also have meant, and four is the full form.
|
|
1222
1290
|
|
|
1223
1291
|
**Parsing is plain segment counting**, the same rule the canonical key follows,
|
|
1224
|
-
and it is sound because every segment is `^[
|
|
1292
|
+
and it is sound because every segment is `^[a-z0-9]+$` — so a hyphen is
|
|
1225
1293
|
purely a separator. A target with five segments is not a hyphenated shortcode;
|
|
1226
1294
|
it is a name, and not an address.
|
|
1227
1295
|
|
|
@@ -2228,7 +2296,7 @@ Three things about the spelling, each of them load-bearing:
|
|
|
2228
2296
|
site chose; a type and a subType are addresses. They need not agree, and on
|
|
2229
2297
|
`sohl` they do not: the section is `user-guide`, because that is a published
|
|
2230
2298
|
URL, while the subType is `userguide` because an address segment is
|
|
2231
|
-
alphanumeric. Both values are checked against that charset here, so
|
|
2299
|
+
lowercase alphanumeric. Both values are checked against that charset here, so
|
|
2232
2300
|
copying the section's name in is refused rather than quietly matching nothing.
|
|
2233
2301
|
- **`listSubType` needs a `listType`.** A subType only tells pages apart within
|
|
2234
2302
|
a type — `rules`, `userguide` and `reference` are all `doc` — so alone it
|
|
@@ -2316,6 +2384,131 @@ None of them exits the process from inside the library; the command decides. Tha
|
|
|
2316
2384
|
is what makes them testable, which the consumer scripts' inline `process.exit`
|
|
2317
2385
|
calls were not.
|
|
2318
2386
|
|
|
2387
|
+
## Publishing a book
|
|
2388
|
+
|
|
2389
|
+
The third surface the content tree publishes, beside the compendium packs and
|
|
2390
|
+
the website: one PDF, built by `content-build pdf` and by `package-build release`.
|
|
2391
|
+
|
|
2392
|
+
```bash
|
|
2393
|
+
npx content-build pdf # build it where `pdf.out` says
|
|
2394
|
+
npx content-build pdf --out build/book # somewhere else
|
|
2395
|
+
npx content-build pdf --no-compile # emit the Typst source and stop
|
|
2396
|
+
npx content-build pdf --version 1.4.0 # stamp a version on the title page
|
|
2397
|
+
```
|
|
2398
|
+
|
|
2399
|
+
### A book is a selection, not a rendering of everything
|
|
2400
|
+
|
|
2401
|
+
The packs and the website publish the _whole_ tree: every note becomes a
|
|
2402
|
+
document and a page, and the three surfaces agreeing about what the content is
|
|
2403
|
+
is the point. **A book is not that.** It is a declared structure whose leaves
|
|
2404
|
+
pick notes out of the corpus, interleaved with prose that need not live in the
|
|
2405
|
+
content tree at all.
|
|
2406
|
+
|
|
2407
|
+
`pdf.document` names that structure — a YAML file of nested sections, each
|
|
2408
|
+
holding some mixture of prose files and `filter:` clauses:
|
|
2409
|
+
|
|
2410
|
+
```yaml
|
|
2411
|
+
contents:
|
|
2412
|
+
- sectionName: Gear
|
|
2413
|
+
contents:
|
|
2414
|
+
- file: prose/gear-preamble.md
|
|
2415
|
+
- filter: "type = 'weapongear'"
|
|
2416
|
+
- sectionName: Armour
|
|
2417
|
+
contents:
|
|
2418
|
+
- filter: "type = 'armorgear'"
|
|
2419
|
+
- sectionName: The Cast
|
|
2420
|
+
contents:
|
|
2421
|
+
- filter: "type = 'being' AND subType = 'npc'"
|
|
2422
|
+
```
|
|
2423
|
+
|
|
2424
|
+
Three consequences follow, and each is behaviour rather than oversight:
|
|
2425
|
+
|
|
2426
|
+
- **A note no clause selects is not in the book.** A project decides what its
|
|
2427
|
+
own volume carries, so an omission is an editorial act and nothing reports it.
|
|
2428
|
+
- **A note several clauses select appears several times**, each occurrence its
|
|
2429
|
+
own page, outline node and anchor. Inbound wikilinks are pointed at the
|
|
2430
|
+
first, so `[[weapongear-dagger]]` reaches one place however often it prints.
|
|
2431
|
+
- **A filter that selects nothing _is_ reported.** A note nobody asked for is
|
|
2432
|
+
expected; a clause matching nothing is either wrong or left over from a
|
|
2433
|
+
structure that has moved on.
|
|
2434
|
+
|
|
2435
|
+
The `WHERE` clause runs against the same content index the SQL tables read, and
|
|
2436
|
+
the build owns the `SELECT … FROM notes` — so a filter cannot name a table, and
|
|
2437
|
+
cannot reach another package's notes.
|
|
2438
|
+
|
|
2439
|
+
### What it is fenced by
|
|
2440
|
+
|
|
2441
|
+
**`publish.site` decides whether a book is built, and it is the only switch.**
|
|
2442
|
+
`content` builds one; `homepage` does not — the same fence that stops the tree
|
|
2443
|
+
being walked for pages stops it being walked for a book. A PDF of the content
|
|
2444
|
+
tree is a content surface by any reading, arguably the most portable one there
|
|
2445
|
+
is, so a package that publishes only a homepage publishes no book however its
|
|
2446
|
+
`pdf:` block is written. The command says so and exits 0.
|
|
2447
|
+
|
|
2448
|
+
That is also the opt-out: a package with no `pdf:` block, no content tree, or
|
|
2449
|
+
`publish.site: homepage` builds nothing and fails nothing.
|
|
2450
|
+
|
|
2451
|
+
### Configuration
|
|
2452
|
+
|
|
2453
|
+
```yaml
|
|
2454
|
+
pdf:
|
|
2455
|
+
title: The Hârn Ensemble # required
|
|
2456
|
+
subtitle: a roster of the ready-made
|
|
2457
|
+
document: book.yaml # required — the document tree above
|
|
2458
|
+
out: build/dist # default
|
|
2459
|
+
front: # prose before the contents
|
|
2460
|
+
- prose/colophon.md
|
|
2461
|
+
fonts:
|
|
2462
|
+
serif: Libertinus Serif
|
|
2463
|
+
sans: Libertinus Sans
|
|
2464
|
+
mono: DejaVu Sans Mono
|
|
2465
|
+
path: assets/fonts # where to find them, beyond the system's
|
|
2466
|
+
iconFonts: # icon family → the font carrying its glyphs
|
|
2467
|
+
fontawesome: assets/fonts/fa-solid-900.ttf
|
|
2468
|
+
binary: typst # when it is not simply `typst` on PATH
|
|
2469
|
+
```
|
|
2470
|
+
|
|
2471
|
+
Nothing here is an address or a brand: the title, the front matter and the faces
|
|
2472
|
+
are the publishing repository's to choose, which is why they are configuration.
|
|
2473
|
+
|
|
2474
|
+
### Typst is a binary, not a dependency
|
|
2475
|
+
|
|
2476
|
+
The compiler is an external program, found on `PATH` or named by `pdf.binary`.
|
|
2477
|
+
Bundling a native compiler would put a platform-specific artefact into the
|
|
2478
|
+
dependency tree of three repositories, only one of which is mostly a book, and
|
|
2479
|
+
it would have to resolve on every consumer's CI runner before any of them could
|
|
2480
|
+
install the toolchain at all.
|
|
2481
|
+
|
|
2482
|
+
A missing binary is a **finding**, not a failure: the `.typ` source is written
|
|
2483
|
+
anyway, which is both the diagnostic and the thing a consumer can compile by
|
|
2484
|
+
hand.
|
|
2485
|
+
|
|
2486
|
+
### What the book gets right, and why each matters
|
|
2487
|
+
|
|
2488
|
+
| Property | How |
|
|
2489
|
+
| ------------------------ | ----------------------------------------------------------------------------- |
|
|
2490
|
+
| Searchable | Real text, not page images — a roster nobody can search for a name is no use. |
|
|
2491
|
+
| Bookmark outline | Every section and entry is a heading, so a viewer's sidebar is the way in. |
|
|
2492
|
+
| Page-numbered contents | `#outline()`, shallower than the bookmarks — 2,500 entries would be 40 pages. |
|
|
2493
|
+
| Repeating table headers | `table.header`, so a property table spilling a page keeps its column names. |
|
|
2494
|
+
| Internal cross-reference | A wikilink between two notes of the book becomes an internal destination. |
|
|
2495
|
+
| External cross-reference | A cross-package link, and a note the book did not select, stay URLs. |
|
|
2496
|
+
|
|
2497
|
+
`{#anchor}` on a heading becomes an internal destination namespaced by its
|
|
2498
|
+
entry, so `[[being-jaslyne#appearance]]` reaches the section and two notes may
|
|
2499
|
+
both declare `{#appearance}`. A reference to a destination the book does not
|
|
2500
|
+
carry falls back to the entry that would have held it, or to plain text, and is
|
|
2501
|
+
reported — because Typst treats a dangling reference as fatal, and one mistyped
|
|
2502
|
+
anchor should not take a thousand-page book down at the last step.
|
|
2503
|
+
|
|
2504
|
+
### The charset is what makes a face choosable
|
|
2505
|
+
|
|
2506
|
+
A PDF embeds the faces it sets, so every character in the corpus is a claim on
|
|
2507
|
+
the book's typeface — and Typst does not warn about a missing glyph, it falls
|
|
2508
|
+
back and exits 0. That is why `content-build lint` holds content to a charset,
|
|
2509
|
+
and why `:icon-…:` names an icon rather than pasting one. Both exist for this
|
|
2510
|
+
surface. See _Prose: formatting and markdown_.
|
|
2511
|
+
|
|
2319
2512
|
## Diffing published addresses
|
|
2320
2513
|
|
|
2321
2514
|
```bash
|
package/README.md
CHANGED
|
@@ -317,6 +317,13 @@ other's schema — they split by input, and the dependency runs one way.
|
|
|
317
317
|
| the release artifact | `packageKind` — a system ships `system.json`, a module `module.json` |
|
|
318
318
|
| the bundle entry | `packageId` — `<id>.mjs`, unless `packageBuild.bundle.entry` says otherwise |
|
|
319
319
|
|
|
320
|
+
**`packageKind: documentation` has no packaging half.** That kind publishes a
|
|
321
|
+
website and a book and installs into no Foundry data directory, so there is no
|
|
322
|
+
artifact to name and no id to derive one from: `manifest` refuses rather than
|
|
323
|
+
writing a manifest for a package Foundry never installs, and the whole of such a
|
|
324
|
+
package's build is `content-build`. See
|
|
325
|
+
[A package that compiles nothing](CONTENT.md#a-package-that-compiles-nothing).
|
|
326
|
+
|
|
320
327
|
## Command line
|
|
321
328
|
|
|
322
329
|
```
|
|
@@ -327,12 +334,23 @@ npx package-build lang check
|
|
|
327
334
|
npx package-build lang coverage [--unused]
|
|
328
335
|
npx package-build lang hardcoded
|
|
329
336
|
npx package-build bundle check
|
|
330
|
-
npx package-build release
|
|
337
|
+
npx package-build release [--no-pdf]
|
|
331
338
|
npx package-build deploy <stage>
|
|
332
339
|
npx package-build container <stage> <start|stop|restart|recreate|rm|status|logs|pull>
|
|
333
340
|
npx package-build e2e <seed|run|open|fast|sweep>
|
|
334
341
|
```
|
|
335
342
|
|
|
343
|
+
`package-build release` writes `<artifact>.zip` and `<artifact>.json` into
|
|
344
|
+
`build/dist`, the content index the manifest advertises, and — when the package
|
|
345
|
+
publishes a content tree — the book that tree compiles to, as
|
|
346
|
+
`<artifact>-<version>.pdf`.
|
|
347
|
+
|
|
348
|
+
The book is **built by default and never fatal**. A package publishing only a
|
|
349
|
+
homepage, one with no `pdf:` block, and one with no content tree each build
|
|
350
|
+
none and say so; `--no-pdf` is for a release that has a tree and does not want
|
|
351
|
+
the book. What the book is and how it is configured is `CONTENT.md`, under
|
|
352
|
+
_Publishing a book_ — it is a content surface, and the content half owns it.
|
|
353
|
+
|
|
336
354
|
Wrapped as npm scripts — SoHL spells them:
|
|
337
355
|
|
|
338
356
|
```json
|