@heroiclands/package-build 20.6.0 → 21.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 +181 -0
- package/CONTENT.md +134 -44
- package/bin/content-build.mjs +37 -5
- package/bin/package-build.mjs +77 -0
- package/content-config.mjs +59 -1
- package/docs/api.md +149 -19
- package/docs/commands.md +75 -0
- package/docs/configuration.md +37 -10
- package/docs/content-format.md +450 -49
- package/engine/content-format.mjs +52 -3
- package/engine/content-images.mjs +699 -0
- package/engine/dependency-bump.mjs +218 -0
- package/engine/frontmatter-lint.mjs +89 -2
- package/engine/helpers.mjs +81 -142
- package/engine/index.mjs +15 -0
- package/engine/infobox-registry.mjs +81 -0
- package/engine/infobox-render.mjs +381 -0
- package/engine/infobox.mjs +963 -0
- package/engine/item-registry.mjs +5 -5
- package/engine/journals.mjs +22 -1
- package/engine/map-notes.mjs +11 -5
- package/engine/metadata-index.mjs +5 -0
- package/engine/note-vocabulary.mjs +57 -2
- package/engine/pathnames.mjs +374 -0
- package/engine/pdf-build.mjs +208 -9
- package/engine/pdf-render.mjs +461 -21
- package/engine/pdf-toc.mjs +77 -5
- package/engine/scenes.mjs +2 -1
- package/engine/site-build.mjs +115 -8
- package/engine/site-index.mjs +93 -4
- package/engine/wikilinks.mjs +93 -0
- package/hm3/default-item-art.mjs +14 -15
- package/hm3/index.mjs +3 -0
- package/hm3/infobox.mjs +64 -0
- package/package.json +3 -2
- package/sohl/being-info.mjs +9 -3
- package/sohl/default-item-art.mjs +18 -16
- package/sohl/index.mjs +3 -0
- package/sohl/infobox.mjs +499 -0
- package/types/content-config.d.mts +7 -0
- package/types/engine/content-format.d.mts +36 -0
- package/types/engine/content-images.d.mts +281 -0
- package/types/engine/dependency-bump.d.mts +89 -0
- package/types/engine/frontmatter-lint.d.mts +23 -0
- package/types/engine/helpers.d.mts +30 -72
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +36 -0
- package/types/engine/infobox-render.d.mts +87 -0
- package/types/engine/infobox.d.mts +443 -0
- package/types/engine/item-registry.d.mts +5 -5
- package/types/engine/journals.d.mts +9 -1
- package/types/engine/note-vocabulary.d.mts +51 -0
- package/types/engine/pathnames.d.mts +189 -0
- package/types/engine/pdf-build.d.mts +46 -0
- package/types/engine/pdf-render.d.mts +99 -1
- package/types/engine/pdf-toc.d.mts +10 -5
- package/types/engine/site-build.d.mts +19 -3
- package/types/engine/site-index.d.mts +35 -3
- package/types/engine/wikilinks.d.mts +22 -0
- package/types/hm3/default-item-art.d.mts +5 -6
- package/types/hm3/index.d.mts +1 -0
- package/types/hm3/infobox.d.mts +22 -0
- package/types/sohl/being-info.d.mts +4 -3
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/infobox.d.mts +145 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,186 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 21.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 742861f: **A pathname names the package that owns the file, and every surface derives its
|
|
8
|
+
own address from it.** A note states a file once — `img:`, `data.portrait:`, a
|
|
9
|
+
map's background, overlay, tile textures and ambient sounds, and the address of
|
|
10
|
+
every image in a body — and four surfaces resolve it: a Foundry install, the
|
|
11
|
+
repository's own tree, the website, and the book. The website used to publish
|
|
12
|
+
the address exactly as authored, so a picture that appeared in Foundry and in
|
|
13
|
+
the book 404'd on the page.
|
|
14
|
+
|
|
15
|
+
**What a pathname looks like now**
|
|
16
|
+
|
|
17
|
+
- `images/map.webp` — a file **this** package ships.
|
|
18
|
+
- `sohl/assets/icons/noun/shield.svg` — a file the `sohl` package ships. The
|
|
19
|
+
first segment is the **content package**, never its Foundry id: `thalorna`,
|
|
20
|
+
not `sohl-thalorna`.
|
|
21
|
+
- An absolute URL, a `data:` URI, a `//host/…` or a `/`-rooted path passes
|
|
22
|
+
through untouched on every surface, which is how a note addresses core Foundry
|
|
23
|
+
art or a package outside this constellation.
|
|
24
|
+
|
|
25
|
+
`thalorna` writing `images/map.webp` publishes
|
|
26
|
+
`modules/sohl-thalorna/assets/images/map.webp` in Foundry,
|
|
27
|
+
`https://cdn.heroiclands.org/thalorna/images/map.webp` on the web, and a copy
|
|
28
|
+
staged at `assets/images/map.webp` in the book.
|
|
29
|
+
|
|
30
|
+
**What every consuming repository must change**
|
|
31
|
+
|
|
32
|
+
- **Convert authored pathnames.** `systems/sohl/assets/X` becomes
|
|
33
|
+
`sohl/assets/X`, and `systems/hm3/images/X` becomes `hm3/assets/images/X`.
|
|
34
|
+
A Foundry-spelled pathname is now refused with a located error naming its
|
|
35
|
+
replacement, so nothing converts silently and nothing is missed.
|
|
36
|
+
- **Set `site.assets`** in `package-build.config.yaml` to the host the site
|
|
37
|
+
serves imagery from. A package-owned image on a page with none set is an
|
|
38
|
+
error naming the key.
|
|
39
|
+
- **HM3 serves its pictures from `assets/images/`.** The default art this
|
|
40
|
+
toolchain pairs with each HM3 item type addresses them there.
|
|
41
|
+
- **`DEFAULT_ITEM_ART` holds pathnames, not install paths.** A runtime reading
|
|
42
|
+
the map directly resolves the pathname for itself.
|
|
43
|
+
- **A package addressed by a pathname must be one the build knows** — its own,
|
|
44
|
+
a game system it compiles content for, or a package under `relationships`.
|
|
45
|
+
Where another package's content name differs from its Foundry id, say so with
|
|
46
|
+
`relationships.<kind>[].contentPackage`.
|
|
47
|
+
|
|
48
|
+
`banner:` is unchanged, and still not a pathname: it names a hero image on the
|
|
49
|
+
site's own asset host, reaches no compiled document and no book, and the Hugo
|
|
50
|
+
theme resolves it.
|
|
51
|
+
|
|
52
|
+
### Minor Changes
|
|
53
|
+
|
|
54
|
+
- 0d83c42: **Beings**
|
|
55
|
+
|
|
56
|
+
- A being note says what kind of being it is — `character` or `creature` — as a
|
|
57
|
+
tag, declared alongside the stations, the place kinds and `draft` that a note's
|
|
58
|
+
tags are already checked against.
|
|
59
|
+
- A being is one or the other. A note carrying both is refused, naming the note
|
|
60
|
+
and the line, because two answers to one question are no answer. A being
|
|
61
|
+
carrying neither is left alone, so a setting part-way through classifying its
|
|
62
|
+
cast still builds.
|
|
63
|
+
- A misspelt kind is caught the way a misspelt `village` already is: a query for
|
|
64
|
+
the characters in a setting does not find `charcter`, and the list it returns
|
|
65
|
+
still looks complete.
|
|
66
|
+
- 841b98b: **`package-build bump` takes a newer first-party release without reformatting
|
|
67
|
+
the lockfile.** Run it with no arguments to move every `@heroiclands/*`
|
|
68
|
+
dependency to its newest published version, or name the packages to move; add
|
|
69
|
+
`--check` to see what would change and write nothing.
|
|
70
|
+
|
|
71
|
+
npm does the resolving, so a version whose dependency set differs from the one
|
|
72
|
+
it replaces is handled as correctly as one that moves three lines — and then
|
|
73
|
+
both `package-lock.json` and `package.json` are restored to the indentation they
|
|
74
|
+
already used. That is the part worth having: every repository consuming this
|
|
75
|
+
toolchain writes its lockfile with four spaces and prettier-ignores it, npm
|
|
76
|
+
rewrites it with two, and a three-line version change arrives as a whole-file
|
|
77
|
+
reformat nobody can review.
|
|
78
|
+
- 5d46b86: **The page**
|
|
79
|
+
|
|
80
|
+
- Every entry now opens a page of its own, under a full-bleed plate carrying the section it belongs to above its name. A wrapped name grows the plate rather than pushing that line off the top of it.
|
|
81
|
+
- A section opens a page of its own too, plated to twice the depth, so the book reads as parts rather than as one continuous run.
|
|
82
|
+
- The body is set in two columns, on cream stock in dark ink with rust rules. A note's `description:` sets as an epigraph under the plate, and the prose opens on a raised capital.
|
|
83
|
+
- A running foot carries the section, an ornament and the folio, so a page found by chance says where it belongs.
|
|
84
|
+
- A page number in the contents now points at the first page of the thing it names.
|
|
85
|
+
- Columns are print's alone. The website keeps one measure.
|
|
86
|
+
|
|
87
|
+
**Tables and wide content**
|
|
88
|
+
|
|
89
|
+
- A table of more than three columns is set across the page instead of crushed into a column: floated over both where it fits a page, and given single-column pages of its own where it does not.
|
|
90
|
+
- A narrower table sets in the column measure and carries its header onto every page and column it spills onto.
|
|
91
|
+
|
|
92
|
+
**What a section can say about itself**
|
|
93
|
+
|
|
94
|
+
- A section may name the banner its plate is drawn over, the line printed above each entry's name, the name the running foot carries, and how many columns its pages are set in. Everything beneath it agrees unless it says otherwise.
|
|
95
|
+
- A section that names no banner still gets its plate, its kicker and its title. A banner that cannot be read is reported.
|
|
96
|
+
- 790d7ab: **An image says how wide it is and where it sits, once, in the note.** A
|
|
97
|
+
markdown image can carry a directive in the curly-attribute convention Pandoc
|
|
98
|
+
and Kramdown use, and the book, the website and a Foundry journal page each
|
|
99
|
+
honour it:
|
|
100
|
+
|
|
101
|
+
```markdown
|
|
102
|
+
{float: top-left}
|
|
103
|
+
|
|
104
|
+
{.full-width}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- **Width is a class**, and the ordinary width carries no marker — `.full-width`
|
|
108
|
+
is the only one there is. One column in the book, the text measure on the
|
|
109
|
+
website and in a journal page.
|
|
110
|
+
- **Position is `float:`**, taking `top-left`, `bottom-left`, `top-right`,
|
|
111
|
+
`bottom-right` or `center`. The website and Foundry wrap text around it; print
|
|
112
|
+
cannot wrap around a shape, so a float there occupies the measure and only the
|
|
113
|
+
top-or-bottom half of a position has an effect.
|
|
114
|
+
- **An image is a block** — it stands alone in its paragraph — and its **alt
|
|
115
|
+
text is the caption**, drawn under the picture on every surface.
|
|
116
|
+
- Both vocabularies are **closed**. `{.fullwidth}`, `{width=800}` and
|
|
117
|
+
`{float: middle}` are refused by `content-build lint`, located by file, line
|
|
118
|
+
and column, and they fail the run: an unrecognised value rendering as the
|
|
119
|
+
ordinary width looks exactly like a directive that worked. Nothing beyond the
|
|
120
|
+
two vocabularies and the address reaches emitted markup.
|
|
121
|
+
|
|
122
|
+
**The book prints pictures.** An image authored in a note is copied into the
|
|
123
|
+
output directory and set at the measure its class names, where it used to reach
|
|
124
|
+
the page as italic alt text. An address naming a file the package does not ship
|
|
125
|
+
prints its caption alone and is reported.
|
|
126
|
+
|
|
127
|
+
**A body image's address follows the `img:` rule** — its first segment says
|
|
128
|
+
which package owns the file — so `images/map.webp` reaches a journal page as
|
|
129
|
+
`modules/<package>/assets/images/map.webp`. The website passes an address
|
|
130
|
+
through as authored; one that has to resolve there is written as a full URL.
|
|
131
|
+
|
|
132
|
+
_Styling the two HTML surfaces is each surface's own: a figure carries
|
|
133
|
+
`note-image`, plus `note-image-full-width` and `note-image-float-…` from the
|
|
134
|
+
vocabularies._
|
|
135
|
+
- 086fbb4: **Infoboxes**
|
|
136
|
+
|
|
137
|
+
- Every entry now opens with a summary panel: the subject's own facts first, then one panel for each game system the note reaches.
|
|
138
|
+
- A gear item's price, weight and durability reach the panel whichever way the note states them, each with its unit on it — 160d, 1.1 lbs.
|
|
139
|
+
- Armour states all four protection aspects, an unrated one reading `0`; a weapon states its strike modes one per line, with `—` where a value was never given.
|
|
140
|
+
- A being's system panel gathers attributes, skills, mystical abilities and equipment, each a section that stays whole when the panel breaks across a page or column.
|
|
141
|
+
- Skills group by the family each skill note declares, and equipment by what the gear is. A projectile states what it hits for.
|
|
142
|
+
- Rows are labelled for a reader rather than named after the field behind them, and the panel says what the note says and nothing else: a value the system would have assumed anyway is left off, and so is a word standing in for an unset one — no more _Potency: Na_, and a strike mode that rolls no damage says so rather than printing a die nobody can roll.
|
|
143
|
+
- A system panel is never a heading over nothing. One reads _Not available_ where the system produces no document for the note; one that produces a document but adds nothing to the profile says so. A system with no such concept draws no panel at all.
|
|
144
|
+
- The panel carries no picture. An image belongs in the note's text, where its position decides what comes after it.
|
|
145
|
+
- On the web and in Foundry each panel is a disclosure a reader can fold away, open to begin with, so several stack without burying the prose. In print they simply flow.
|
|
146
|
+
- What a panel holds is settled once and drawn the same way in the book, on the website and in a compendium journal — and a field added to a note type appears in all three without further work.
|
|
147
|
+
|
|
148
|
+
**Content format**
|
|
149
|
+
|
|
150
|
+
- The specification now describes the infobox: where it sits in each medium, the rules that hold everywhere, which fields each panel carries, the three things a system panel can say, and the shape a page publishes it in.
|
|
151
|
+
|
|
152
|
+
### Patch Changes
|
|
153
|
+
|
|
154
|
+
- 841b98b: **A being that declares no `sohl:` block no longer takes the site build down.**
|
|
155
|
+
`content-build site` failed with `unacceptable kind of an object to dump [object
|
|
156
|
+
Undefined]` for any `type: being` note whose front matter carried no `sohl:` key
|
|
157
|
+
at all, and the throw aborted the entire run rather than the one page. A note
|
|
158
|
+
carrying `sohl: null` had always been fine, so the failure only appeared once a
|
|
159
|
+
tree removed the empty key rather than emptying it. Both shapes now publish the
|
|
160
|
+
same page. Fixes HeroicLands/package-build#478.
|
|
161
|
+
|
|
162
|
+
## 20.7.0
|
|
163
|
+
|
|
164
|
+
### Minor Changes
|
|
165
|
+
|
|
166
|
+
- 6c10f17: **A `trees` entry's pages stay where their tree mounts them** — declaring
|
|
167
|
+
`subType` on a note inside `site.trees` no longer moves its published address.
|
|
168
|
+
A `subType` classifies a page for the site index; it never routed a tree
|
|
169
|
+
page's URL to begin with for a note that left it unset, and now it does not
|
|
170
|
+
for one that sets it either. A site that already worked around this by
|
|
171
|
+
avoiding `subType` on its tree notes can declare it again: those pages move
|
|
172
|
+
back under the tree's own section on upgrade.
|
|
173
|
+
|
|
174
|
+
### Patch Changes
|
|
175
|
+
|
|
176
|
+
- 72daae4: **A being that declares no `sohl:` block no longer takes the site build down.**
|
|
177
|
+
`content-build site` failed with `unacceptable kind of an object to dump [object
|
|
178
|
+
Undefined]` for any `type: being` note whose front matter carried no `sohl:` key
|
|
179
|
+
at all, and the throw aborted the entire run rather than the one page. A note
|
|
180
|
+
carrying `sohl: null` had always been fine, so the failure only appeared once a
|
|
181
|
+
tree removed the empty key rather than emptying it. Both shapes now publish the
|
|
182
|
+
same page. Fixes HeroicLands/package-build#478.
|
|
183
|
+
|
|
3
184
|
## 20.6.0
|
|
4
185
|
|
|
5
186
|
### Minor Changes
|
package/CONTENT.md
CHANGED
|
@@ -259,9 +259,9 @@ What follows from the kind:
|
|
|
259
259
|
- **`foundryPackage` is not derived.** For every other kind the loader reads it
|
|
260
260
|
from the adjacent `package.json`; there is no Foundry package here to carry an
|
|
261
261
|
id, so `foundryPackage` and `assetRoot` are both `null`. A note's `img:`
|
|
262
|
-
therefore names
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
therefore names a `/`-rooted path or a URL — a pathname this package would
|
|
263
|
+
have to serve itself is refused, because Foundry serves no files for a
|
|
264
|
+
package it does not install.
|
|
265
265
|
- **`package-build manifest` refuses**, rather than emitting a `module.json`
|
|
266
266
|
advertising an installable package with no id, no packs and no compatibility
|
|
267
267
|
range. So does `content-build package compile`, which would otherwise exit 0
|
|
@@ -709,44 +709,57 @@ whitelist without a builder behind it.
|
|
|
709
709
|
same `resolveImg` rule as a note's `img:`, so one spelling means one thing
|
|
710
710
|
wherever it is written.
|
|
711
711
|
|
|
712
|
-
####
|
|
713
|
-
|
|
714
|
-
Every authored
|
|
715
|
-
`portrait
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
712
|
+
#### A pathname names the package that owns the file
|
|
713
|
+
|
|
714
|
+
Every authored pathname — a registry `img:`, a note's `img:`, an actor's
|
|
715
|
+
`data.portrait:`, a map's background, the address of an image in a body —
|
|
716
|
+
answers "which package holds this file?" in its **first segment**, when an
|
|
717
|
+
`assets/` follows it. Everything after that `assets/` is the _suffix_, and a
|
|
718
|
+
pathname that does not open `<package>/assets/` belongs to the package being
|
|
719
|
+
compiled, whole.
|
|
720
|
+
|
|
721
|
+
Four surfaces derive an address from that one statement. For a `thalorna` note
|
|
722
|
+
— the package Foundry installs as the module `sohl-thalorna` — writing
|
|
723
|
+
`images/map.webp`:
|
|
724
|
+
|
|
725
|
+
| Surface | Address |
|
|
726
|
+
| ----------- | -------------------------------------------------------- |
|
|
727
|
+
| **Foundry** | `modules/sohl-thalorna/assets/images/map.webp` |
|
|
728
|
+
| **Local** | `assets/images/map.webp` |
|
|
729
|
+
| **Web** | `https://cdn.heroiclands.org/thalorna/images/map.webp` |
|
|
730
|
+
| **Book** | `assets/images/map.webp`, staged beside the Typst source |
|
|
731
|
+
|
|
732
|
+
`sohl/assets/icons/relic.svg` names the `sohl` package's file wherever it is
|
|
733
|
+
written, which is what lets a module pair a SoHL default with one of its own
|
|
734
|
+
types. The package's name is not its Foundry id: `thalorna` is the content and
|
|
735
|
+
`sohl-thalorna` the install, and only the Foundry form carries the second.
|
|
736
|
+
|
|
737
|
+
The web host is `site.assets`, and a package-owned image on a page with none
|
|
738
|
+
set is an error naming that key.
|
|
739
|
+
|
|
740
|
+
**The rule is about ownership, not a list of directories.** A package owns its
|
|
735
741
|
whole `assets/` tree, so a directory this toolchain has never heard of is still
|
|
736
742
|
that package's: art under `assets/artwork/` is addressed `artwork/deity.webp`
|
|
737
|
-
and
|
|
738
|
-
at all — an absolute URL, a `data:` URI, a `/`-rooted path — passes
|
|
739
|
-
the same rule rather than as an exception.
|
|
743
|
+
and resolved exactly as `icons/…` and `images/…` are. An address naming no
|
|
744
|
+
package at all — an absolute URL, a `data:` URI, a `/`-rooted path — passes
|
|
745
|
+
through on every surface, on the same rule rather than as an exception.
|
|
746
|
+
|
|
747
|
+
**A `systems/…` or `modules/…` pathname is refused**, with a finding naming the
|
|
748
|
+
replacement: it resolves for Foundry and for neither of the other surfaces, and
|
|
749
|
+
deriving anything from it would put a wrong address on two of the three.
|
|
750
|
+
`worlds/` is not such a root — a package may not ship files out of a world, so
|
|
751
|
+
that path gets the ordinary reading and produces a plainly broken one rather
|
|
752
|
+
than a plausible one that 404s unreported.
|
|
740
753
|
|
|
741
|
-
`
|
|
742
|
-
|
|
743
|
-
one that 404s in Foundry unreported.
|
|
754
|
+
A `documentation` package installs nothing, so it has no Foundry form to derive
|
|
755
|
+
and addresses another package's file by a `/`-rooted path or a URL.
|
|
744
756
|
|
|
745
757
|
**`banner:` is a path that does not follow this rule.** It reaches no compiled
|
|
746
|
-
document; it is a top-level key the Hugo theme reads, and the theme
|
|
747
|
-
relative value with `images/` and joins it onto
|
|
748
|
-
address different places —
|
|
749
|
-
|
|
758
|
+
document and no book; it is a top-level key the Hugo theme reads, and the theme
|
|
759
|
+
prefixes a relative value with `images/` and joins it onto
|
|
760
|
+
`params.cdnBaseURL`. The two address different places — a pathname a file a
|
|
761
|
+
package ships, `banner:` a hero image on the site's own asset host — so they are
|
|
762
|
+
stated apart rather than reconciled. See the
|
|
750
763
|
[content format specification](docs/content-format.md#banner-addresses-the-cdn-not-the-foundry-install).
|
|
751
764
|
|
|
752
765
|
#### "Names no art" and "wants no art" are different
|
|
@@ -2436,6 +2449,75 @@ The `WHERE` clause runs against the same content index the SQL tables read, and
|
|
|
2436
2449
|
the build owns the `SELECT … FROM notes` — so a filter cannot name a table, and
|
|
2437
2450
|
cannot reach another package's notes.
|
|
2438
2451
|
|
|
2452
|
+
### How the book is set
|
|
2453
|
+
|
|
2454
|
+
**Every entry opens a page of its own**, under a full-bleed plate carrying a
|
|
2455
|
+
small-caps kicker above the entry's name. A reference book is consulted rather
|
|
2456
|
+
than read through: an entry beginning halfway down a page is harder to find,
|
|
2457
|
+
cannot carry its own running head honestly, and makes a page number in the
|
|
2458
|
+
contents point at the middle of something else. A section opens a page of its
|
|
2459
|
+
own too, plated to twice the depth, so a section reads as a section rather than
|
|
2460
|
+
as the first entry beneath it.
|
|
2461
|
+
|
|
2462
|
+
**The body is set in two columns**, and every other measure follows from that
|
|
2463
|
+
one: an image with no width class is a column wide, the infobox flows in the
|
|
2464
|
+
column measure and breaks between its sections, and a table wider than three
|
|
2465
|
+
columns is set across the page. Two columns are print's answer and print's
|
|
2466
|
+
alone — a scrolling page has no fixed viewport, so the website keeps one
|
|
2467
|
+
measure with a side rail.
|
|
2468
|
+
|
|
2469
|
+
A note's `description:` sets as an epigraph between short rules under the
|
|
2470
|
+
plate, and the first paragraph of the prose opens on a raised capital. Both
|
|
2471
|
+
disappear rather than leave a shell: a note with no description has no
|
|
2472
|
+
epigraph, and a body opening on a link or a number keeps its own first
|
|
2473
|
+
character. The running foot carries the section's name, an ornament and the
|
|
2474
|
+
folio.
|
|
2475
|
+
|
|
2476
|
+
A table of **more than three columns** is given an explicit span rather than
|
|
2477
|
+
left to overflow the measure. Which span depends on how tall it is, and that is
|
|
2478
|
+
measured while the page is laid out: a table that fits a page is floated across
|
|
2479
|
+
both columns at the top of one, and a longer one is set on single-column pages
|
|
2480
|
+
of its own — a float cannot break, and a table taller than the page placed as
|
|
2481
|
+
one piles its rows on top of each other without a word of warning.
|
|
2482
|
+
|
|
2483
|
+
### What a section declares, its entries inherit
|
|
2484
|
+
|
|
2485
|
+
A section may carry presentation alongside its `contents:`, and everything
|
|
2486
|
+
beneath it agrees unless it says otherwise:
|
|
2487
|
+
|
|
2488
|
+
```yaml
|
|
2489
|
+
contents:
|
|
2490
|
+
- sectionName: Beings
|
|
2491
|
+
footer: The Bestiary # what the running foot carries
|
|
2492
|
+
page:
|
|
2493
|
+
banner: assets/images/banners/bestiary.webp # the plate's picture
|
|
2494
|
+
kicker: The Bestiary of Thalorna # the line above each entry's name
|
|
2495
|
+
columns: 2 # the measure this section's pages are set in
|
|
2496
|
+
contents:
|
|
2497
|
+
- filter: "type = 'being'"
|
|
2498
|
+
```
|
|
2499
|
+
|
|
2500
|
+
| Key | Default | What it does |
|
|
2501
|
+
| -------------- | ---------------------- | ----------------------------------------------------- |
|
|
2502
|
+
| `footer` | the section's own name | The name in the running foot. |
|
|
2503
|
+
| `page.banner` | none | A file this repository ships, plated under the title. |
|
|
2504
|
+
| `page.kicker` | the section's trail | The small-caps line above an entry's name. |
|
|
2505
|
+
| `page.columns` | `2` | Columns, from 1 to 4. |
|
|
2506
|
+
|
|
2507
|
+
`page:` is inherited whole: a subsection declaring one of its keys states the
|
|
2508
|
+
others it wants as well.
|
|
2509
|
+
|
|
2510
|
+
**A missing banner is a plate without a picture.** A section plate implies a
|
|
2511
|
+
banner per section, and art arrives later than rendering does — so a section
|
|
2512
|
+
that names none still gets its plate, its kicker and its title, set over the
|
|
2513
|
+
book's ink, and nothing is reported. A banner the build _cannot read_ is a
|
|
2514
|
+
different matter: that is a statement the tree makes and the build cannot
|
|
2515
|
+
honour, so it is a finding.
|
|
2516
|
+
|
|
2517
|
+
`header:` and `infobox:` are part of the format and read by nothing: the
|
|
2518
|
+
running head is a foot in this design, and which infobox a note draws is
|
|
2519
|
+
decided by the note's type.
|
|
2520
|
+
|
|
2439
2521
|
### What it is fenced by
|
|
2440
2522
|
|
|
2441
2523
|
**`publish.site` decides whether a book is built, and it is the only switch.**
|
|
@@ -2485,14 +2567,22 @@ hand.
|
|
|
2485
2567
|
|
|
2486
2568
|
### What the book gets right, and why each matters
|
|
2487
2569
|
|
|
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.
|
|
2570
|
+
| Property | How |
|
|
2571
|
+
| ------------------------ | ------------------------------------------------------------------------------- |
|
|
2572
|
+
| Searchable | Real text, not page images — a roster nobody can search for a name is no use. |
|
|
2573
|
+
| Bookmark outline | Every section and entry is a heading, so a viewer's sidebar is the way in. |
|
|
2574
|
+
| Page-numbered contents | `#outline()`, shallower than the bookmarks — 2,500 entries would be 40 pages. |
|
|
2575
|
+
| Repeating table headers | `table.header`, so a property table spilling a page keeps its column names. |
|
|
2576
|
+
| Internal cross-reference | A wikilink between two notes of the book becomes an internal destination. |
|
|
2577
|
+
| External cross-reference | A cross-package link, and a note the book did not select, stay URLs. |
|
|
2578
|
+
| Illustration | An image authored in the body is staged and set at the measure its class names. |
|
|
2579
|
+
|
|
2580
|
+
An image states its own width and position once, in the note, and the book, the
|
|
2581
|
+
website and a Foundry journal page each honour it — see
|
|
2582
|
+
[Images](docs/content-format.md#images) for the two closed vocabularies. The book
|
|
2583
|
+
copies each picture it prints into the output directory before the compiler
|
|
2584
|
+
runs, because Typst reads nothing above its own root; an address naming a file
|
|
2585
|
+
this package does not ship prints its caption alone and is reported.
|
|
2496
2586
|
|
|
2497
2587
|
`{#anchor}` on a heading becomes an internal destination namespaced by its
|
|
2498
2588
|
entry, so `[[being-jaslyne#appearance]]` reaches the section and two notes may
|
package/bin/content-build.mjs
CHANGED
|
@@ -84,6 +84,7 @@ import { lintContentTree } from "../engine/content-lint.mjs";
|
|
|
84
84
|
import { lintContentCharset } from "../engine/content-charset.mjs";
|
|
85
85
|
import { lintContentHtml } from "../engine/content-html.mjs";
|
|
86
86
|
import { lintContentIcons } from "../engine/content-icons.mjs";
|
|
87
|
+
import { lintContentImages } from "../engine/content-images.mjs";
|
|
87
88
|
import { declaredSystems, lintFrontmatter, systemBlocksFor } from "../engine/frontmatter-lint.mjs";
|
|
88
89
|
import { loadContentFormat } from "../engine/content-format.mjs";
|
|
89
90
|
import {
|
|
@@ -962,6 +963,14 @@ function lintCommand() {
|
|
|
962
963
|
skipDirectories: config.skipDirectories,
|
|
963
964
|
});
|
|
964
965
|
|
|
966
|
+
// The width and position an image states. Refused rather than
|
|
967
|
+
// reported, because an unrecognised value rendering as the
|
|
968
|
+
// ordinary width looks exactly like a directive that worked —
|
|
969
|
+
// so these are errors and they fail the run.
|
|
970
|
+
const images = lintContentImages(root, {
|
|
971
|
+
skipDirectories: config.skipDirectories,
|
|
972
|
+
});
|
|
973
|
+
|
|
965
974
|
const findings = [
|
|
966
975
|
...addresses.findings,
|
|
967
976
|
...frontmatter.findings,
|
|
@@ -969,6 +978,7 @@ function lintCommand() {
|
|
|
969
978
|
...charset.findings,
|
|
970
979
|
...icons.findings,
|
|
971
980
|
...html.findings,
|
|
981
|
+
...images.findings,
|
|
972
982
|
];
|
|
973
983
|
// Only an **error** fails the run. Every finding was an error
|
|
974
984
|
// by then, so this changes nothing on its own —
|
|
@@ -1418,7 +1428,13 @@ function pdfCommand() {
|
|
|
1418
1428
|
compile: argv.compile !== false,
|
|
1419
1429
|
});
|
|
1420
1430
|
|
|
1421
|
-
|
|
1431
|
+
// The shared line, not a second copy of it: an **error** fails
|
|
1432
|
+
// the run and a **warning** does not. A picture the book cannot
|
|
1433
|
+
// carry is a warning — the entry still prints, with its caption
|
|
1434
|
+
// where the image would be — and a pathname no surface can
|
|
1435
|
+
// resolve is an error, because the same statement is wrong in
|
|
1436
|
+
// Foundry and on the website too.
|
|
1437
|
+
const errors = reportFindings(result.findings, {});
|
|
1422
1438
|
|
|
1423
1439
|
if (!result.built) {
|
|
1424
1440
|
// A reason is a deliberate no-op — the fence, an absent
|
|
@@ -1442,9 +1458,7 @@ function pdfCommand() {
|
|
|
1442
1458
|
);
|
|
1443
1459
|
log.info(`Typst source: ${result.typ}`);
|
|
1444
1460
|
if (result.pdf) log.info(`Book: ${result.pdf}`);
|
|
1445
|
-
|
|
1446
|
-
// nothing is worth fixing and is not worth refusing to publish
|
|
1447
|
-
// the other two thousand entries over.
|
|
1461
|
+
if (errors) process.exitCode = 1;
|
|
1448
1462
|
} catch (err) {
|
|
1449
1463
|
reportFailure(err);
|
|
1450
1464
|
process.exitCode = 1;
|
|
@@ -1565,7 +1579,25 @@ function siteCommand() {
|
|
|
1565
1579
|
message: linkFindingMessage(e),
|
|
1566
1580
|
});
|
|
1567
1581
|
}
|
|
1568
|
-
|
|
1582
|
+
// An image whose pathname the site cannot resolve, located the
|
|
1583
|
+
// way a wikilink finding is: by searching the note for the
|
|
1584
|
+
// literal the resolver was handed. The page is written either
|
|
1585
|
+
// way — a missing picture is visible, and stopping before the
|
|
1586
|
+
// write would hide every other finding in the tree behind one
|
|
1587
|
+
// address.
|
|
1588
|
+
for (const e of result.imageErrors) {
|
|
1589
|
+
emitDiagnostic({
|
|
1590
|
+
file: e.file,
|
|
1591
|
+
...positionOfLiteral(readRawNote(e.file), e.src, e.occurrence),
|
|
1592
|
+
severity: "error",
|
|
1593
|
+
message: e.message,
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
if (
|
|
1597
|
+
result.tableErrors.length ||
|
|
1598
|
+
result.wikiErrors.length ||
|
|
1599
|
+
result.imageErrors.length
|
|
1600
|
+
) {
|
|
1569
1601
|
process.exitCode = 1;
|
|
1570
1602
|
return;
|
|
1571
1603
|
}
|
package/bin/package-build.mjs
CHANGED
|
@@ -86,6 +86,7 @@ import { SCHEMA_ARTIFACT_FILE } from "../engine/foreign-catalog.mjs";
|
|
|
86
86
|
import { validateLangSource } from "../lang.mjs";
|
|
87
87
|
import { checkLabelRegistry } from "../labels.mjs";
|
|
88
88
|
import { lintYaml } from "../engine/yaml-lint.mjs";
|
|
89
|
+
import { bumpDependencies } from "../engine/dependency-bump.mjs";
|
|
89
90
|
import {
|
|
90
91
|
analyzeCoverage,
|
|
91
92
|
collectScriptReferences,
|
|
@@ -1132,6 +1133,81 @@ function e2eCommand() {
|
|
|
1132
1133
|
};
|
|
1133
1134
|
}
|
|
1134
1135
|
|
|
1136
|
+
/**
|
|
1137
|
+
* `bump [packages..]` — take a newer version of a dependency.
|
|
1138
|
+
*
|
|
1139
|
+
* npm does the resolving, so a bump whose dependency set changed is as correct
|
|
1140
|
+
* as one that moves three lines. What this adds is the indentation: every
|
|
1141
|
+
* repository here writes `package-lock.json` with four spaces and
|
|
1142
|
+
* prettier-ignores it, and npm rewrites it with two, so the version change
|
|
1143
|
+
* arrives buried in a whole-file reformat.
|
|
1144
|
+
*
|
|
1145
|
+
* Named nothing, it takes the first-party packages — the ones a person bumps
|
|
1146
|
+
* by hand, the moment a release publishes, usually to unblock the change that
|
|
1147
|
+
* prompted it. Third-party bumps arrive from Dependabot on their own schedule.
|
|
1148
|
+
*
|
|
1149
|
+
* @returns {object} The yargs command module.
|
|
1150
|
+
*/
|
|
1151
|
+
function bumpCommand() {
|
|
1152
|
+
return {
|
|
1153
|
+
command: "bump [packages..]",
|
|
1154
|
+
describe: "Take a newer version of a dependency, keeping the lockfile's formatting",
|
|
1155
|
+
builder: (y) =>
|
|
1156
|
+
y
|
|
1157
|
+
.positional("packages", {
|
|
1158
|
+
type: "string",
|
|
1159
|
+
array: true,
|
|
1160
|
+
describe: "Packages to bump (default: every @heroiclands/* dependency)",
|
|
1161
|
+
})
|
|
1162
|
+
.option("tag", {
|
|
1163
|
+
type: "string",
|
|
1164
|
+
default: "latest",
|
|
1165
|
+
describe: "Dist-tag to take",
|
|
1166
|
+
})
|
|
1167
|
+
.option("check", {
|
|
1168
|
+
type: "boolean",
|
|
1169
|
+
default: false,
|
|
1170
|
+
describe: "Report what would change and write nothing",
|
|
1171
|
+
}),
|
|
1172
|
+
handler: handler(async (args) => {
|
|
1173
|
+
const rootDir = process.cwd();
|
|
1174
|
+
const result = bumpDependencies({
|
|
1175
|
+
rootDir,
|
|
1176
|
+
packages: args.packages,
|
|
1177
|
+
tag: args.tag,
|
|
1178
|
+
check: args.check,
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
if (result.changes.length === 0) {
|
|
1182
|
+
const named =
|
|
1183
|
+
result.unchanged.length ?
|
|
1184
|
+
result.unchanged.join(", ")
|
|
1185
|
+
: "no first-party dependency";
|
|
1186
|
+
console.log(`Already on the newest ${args.tag}: ${named}`);
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
for (const { name, from, to } of result.changes) {
|
|
1191
|
+
console.log(`${name} ${from ?? "—"} → ${to}`);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (result.checked) {
|
|
1195
|
+
console.log(
|
|
1196
|
+
`\nRun without --check to take ${result.changes.length > 1 ? "them" : "it"}.`,
|
|
1197
|
+
);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
for (const file of result.reindented) {
|
|
1202
|
+
console.log(` kept the existing indentation of ${path.basename(file)}`);
|
|
1203
|
+
}
|
|
1204
|
+
console.log(
|
|
1205
|
+
`\nInstall it with \`npm ci\`, which resolves from the lockfile this just moved.`,
|
|
1206
|
+
);
|
|
1207
|
+
}),
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1135
1211
|
yargs(hideBin(process.argv))
|
|
1136
1212
|
.scriptName("package-build")
|
|
1137
1213
|
.command(cleanCommand())
|
|
@@ -1140,6 +1216,7 @@ yargs(hideBin(process.argv))
|
|
|
1140
1216
|
.command(schemaCommand())
|
|
1141
1217
|
.command(langCommand())
|
|
1142
1218
|
.command(labelsCommand())
|
|
1219
|
+
.command(bumpCommand())
|
|
1143
1220
|
.command(yamlCommand())
|
|
1144
1221
|
.command(bundleCommand())
|
|
1145
1222
|
.command(releaseCommand())
|