@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
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
This is the path from an empty directory to a HeroicLands package that builds:
|
|
4
|
+
a repository whose notes compile into Foundry compendium packs, whose manifest
|
|
5
|
+
Foundry can install, and whose release archive is ready to attach to a tag.
|
|
6
|
+
|
|
7
|
+
It assumes you know the constellation's conventions — `~/dev/HeroicLands`, the
|
|
8
|
+
branch and pull request rules, the shared Prettier configuration, the git hooks
|
|
9
|
+
— and assumes nothing about this toolchain. Everything specific to
|
|
10
|
+
`@heroiclands/package-build` is stated here or linked.
|
|
11
|
+
|
|
12
|
+
The worked example builds a **module content package**: a Foundry module whose
|
|
13
|
+
whole substance is a content tree. That is the common case, and the shortest
|
|
14
|
+
path that exercises every stage of the build — the note format, the pack
|
|
15
|
+
compilers, the content index, the manifest and the release archive. A system
|
|
16
|
+
package (`packageKind: systems`) differs only in what its manifest is called and
|
|
17
|
+
where its version is derived from; a documentation package is a different shape
|
|
18
|
+
and is covered at the end.
|
|
19
|
+
|
|
20
|
+
You will need Node 24 or newer and npm. Nothing else: no Foundry install, no
|
|
21
|
+
credentials, no container.
|
|
22
|
+
|
|
23
|
+
## Where the reference material is
|
|
24
|
+
|
|
25
|
+
This document is about **order and motivation** — what to do first, and why the
|
|
26
|
+
next step needs the one before it. The detail lives in the references beside it,
|
|
27
|
+
and this tutorial links to them rather than repeating them:
|
|
28
|
+
|
|
29
|
+
| Document | What it answers |
|
|
30
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------- |
|
|
31
|
+
| [`commands.md`](commands.md) | What each command does, its options, its exit codes. |
|
|
32
|
+
| [`configuration.md`](configuration.md) | Every key of `package-build.config.yaml`, its type, its default, its refusals. |
|
|
33
|
+
| [`content-format.md`](content-format.md) | What a note may declare, per note type, and what it compiles into. |
|
|
34
|
+
| [`diagnostics.md`](diagnostics.md) | How to read the `file:line:column: severity: message` output. |
|
|
35
|
+
| [`project-setup.md`](project-setup.md) | The files a repository carries beyond the configuration, and the script wiring. |
|
|
36
|
+
| [`api.md`](api.md) | The programmatic surface, for a repository with a build script of its own. |
|
|
37
|
+
|
|
38
|
+
## The shape of what you are building
|
|
39
|
+
|
|
40
|
+
Nine files, and a directory the build writes:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
acme-bestiary/
|
|
44
|
+
├── package.json # identity: the Foundry package id and the release addresses
|
|
45
|
+
├── package-build.config.yaml # the build: what this package is, and what it compiles
|
|
46
|
+
├── prettier.config.js # the shared formatting options
|
|
47
|
+
├── .gitignore # what is generated, so the checks skip it
|
|
48
|
+
├── README.md # shipped into the package
|
|
49
|
+
├── LICENSE.md # shipped into the package
|
|
50
|
+
├── lang/en.json # shipped into the package
|
|
51
|
+
├── assets/content/ # the notes — the only hand-authored content
|
|
52
|
+
│ ├── homepage.md
|
|
53
|
+
│ └── Bestiary/Marsh_Drake.md
|
|
54
|
+
└── build/ # everything the build writes; never committed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Two facts govern the whole arrangement, and both are worth holding on to before
|
|
58
|
+
the first command.
|
|
59
|
+
|
|
60
|
+
**`package.json` is the package's identity.** The Foundry package id is its
|
|
61
|
+
`name`, verbatim; the manifest, download and bug addresses are derived from its
|
|
62
|
+
`repository.url`; a system's stamped version is its `version`. None of those is
|
|
63
|
+
transcribed into the build configuration, because a transcribed copy is free to
|
|
64
|
+
drift from what it copied.
|
|
65
|
+
|
|
66
|
+
**`package-build.config.yaml` is the build's single source.** The pack list, the
|
|
67
|
+
compatibility range, the manifest and the site layout are all declared there
|
|
68
|
+
once, and the generated `module.json` is derived from it. There is no
|
|
69
|
+
hand-authored manifest to keep in step.
|
|
70
|
+
|
|
71
|
+
**One thing about the examples.** Every block below is a real file or a real
|
|
72
|
+
transcript. Fenced examples render at the two-space indentation markdown itself
|
|
73
|
+
uses, which is not the four the shared Prettier configuration gives a `.json` or
|
|
74
|
+
`.yaml` file — so copy the content and let `content-build format --write` settle
|
|
75
|
+
the whitespace, rather than transcribing it. Step 6 is where that becomes part
|
|
76
|
+
of the routine.
|
|
77
|
+
|
|
78
|
+
## Step 1 — `package.json`
|
|
79
|
+
|
|
80
|
+
Create the directory and write `package.json` first, because the toolchain reads
|
|
81
|
+
it from the moment it is installed.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
mkdir acme-bestiary
|
|
85
|
+
cd acme-bestiary
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"name": "acme-bestiary",
|
|
91
|
+
"private": true,
|
|
92
|
+
"version": "0.1.0",
|
|
93
|
+
"type": "module",
|
|
94
|
+
"license": "GPL-3.0-or-later AND CC-BY-SA-4.0",
|
|
95
|
+
"repository": {
|
|
96
|
+
"type": "git",
|
|
97
|
+
"url": "https://github.com/HeroicLands/acme-bestiary"
|
|
98
|
+
},
|
|
99
|
+
"engines": {
|
|
100
|
+
"node": ">=24.0.0"
|
|
101
|
+
},
|
|
102
|
+
"scripts": {}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Five of those keys are load-bearing:
|
|
107
|
+
|
|
108
|
+
- **`name`** becomes the Foundry package id, read verbatim. It is what appears
|
|
109
|
+
as `id` in the generated `module.json`, what a compendium UUID is addressed
|
|
110
|
+
through (`Compendium.acme-bestiary.journals.…`), and what the served asset
|
|
111
|
+
root is built from. Choose it once; renaming it later invalidates every UUID
|
|
112
|
+
anything has stored.
|
|
113
|
+
- **`version`** is the release version. It appears in the manifest and in the
|
|
114
|
+
pinned `download` address.
|
|
115
|
+
- **`repository.url`** is what the manifest's `url`, `bugs`, `manifest` and
|
|
116
|
+
`download` addresses are derived from. Without it, `package-build manifest`
|
|
117
|
+
refuses to write anything.
|
|
118
|
+
- **`type: "module"`** lets `prettier.config.js` be written as an ES module,
|
|
119
|
+
which is how the shared configuration is re-exported.
|
|
120
|
+
- **`private: true`** because a package built by this toolchain ships as a
|
|
121
|
+
Foundry release archive, not to npm.
|
|
122
|
+
|
|
123
|
+
Let `content-build format --write` set the indentation once this file exists:
|
|
124
|
+
npm matches `package.json`'s existing indentation when it writes
|
|
125
|
+
`package-lock.json`, so a formatted `package.json` produces a formatted
|
|
126
|
+
lockfile, and neither needs a `.prettierignore` entry for the life of the
|
|
127
|
+
repository.
|
|
128
|
+
|
|
129
|
+
## Step 2 — install the toolchain
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm install --save-dev @heroiclands/package-build
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
That installs two binaries into `node_modules/.bin`: `package-build`, the
|
|
136
|
+
packaging half, and `content-build`, the content half. Every command below is
|
|
137
|
+
one of those two. Run them through `npx`, or wire them into npm scripts as
|
|
138
|
+
[`project-setup.md`](project-setup.md) describes.
|
|
139
|
+
|
|
140
|
+
**How it worked:** `npx package-build --version` prints the installed version.
|
|
141
|
+
`--version` and `--help` are the only invocations that do not read a
|
|
142
|
+
configuration file. Every other one resolves `package-build.config.yaml` first,
|
|
143
|
+
by walking up from the working directory, and fails loudly when it finds none:
|
|
144
|
+
|
|
145
|
+
```console
|
|
146
|
+
$ npx content-build package compile
|
|
147
|
+
[ERROR]: package-build: no package-build.config.yaml or package-build.config.yml or package-build.config.mjs found at or above /path/to/acme-bestiary, nor at or above /path/to/acme-bestiary/node_modules/@heroiclands/package-build/engine. A consuming repository declares its build in one file at its root; set PACKAGE_BUILD_CONFIG to name it elsewhere.
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
That is the next step.
|
|
151
|
+
|
|
152
|
+
## Step 3 — `.gitignore` and `prettier.config.js`
|
|
153
|
+
|
|
154
|
+
These come before the first check rather than after it, because the checks read
|
|
155
|
+
them.
|
|
156
|
+
|
|
157
|
+
`.gitignore` is what keeps generated trees out of the prose checks: both
|
|
158
|
+
`content-build format` and `content-build markdown` consult a repository's
|
|
159
|
+
ignore files, so a `build/` that is not named here is a build whose own output
|
|
160
|
+
gets reported as unformatted.
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
node_modules
|
|
164
|
+
/build/
|
|
165
|
+
/nogit/
|
|
166
|
+
/.env.local
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`node_modules` carries neither a leading nor a trailing slash, for a reason
|
|
170
|
+
[`project-setup.md`](project-setup.md) gives in full: a trailing slash matches a
|
|
171
|
+
directory only, and a worktree whose `node_modules` is a symlink then goes
|
|
172
|
+
unignored.
|
|
173
|
+
|
|
174
|
+
`prettier.config.js` re-exports the shared options:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
/**
|
|
178
|
+
* The shared HeroicLands Prettier configuration.
|
|
179
|
+
*
|
|
180
|
+
* Every route to Prettier — the toolchain, an editor integration, a bare
|
|
181
|
+
* `npx prettier` — resolves a config file, so re-exporting the shared options
|
|
182
|
+
* here is what keeps those three from formatting the same tree three ways.
|
|
183
|
+
*
|
|
184
|
+
* @type {import("prettier").Config}
|
|
185
|
+
*/
|
|
186
|
+
export { default } from "@heroiclands/package-build/prettier";
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`content-build format` applies the shared options whether or not this file
|
|
190
|
+
exists, so the lint chain is correct without it. Nothing else is: an editor's
|
|
191
|
+
format-on-save and a bare `npx prettier --write .` resolve a _config file_, and
|
|
192
|
+
finding none they fall back to Prettier's own defaults — a different print width
|
|
193
|
+
against the same tree, so the editor and the lint chain take turns rewriting
|
|
194
|
+
each other's work. The toolchain says so when the file is missing:
|
|
195
|
+
|
|
196
|
+
```console
|
|
197
|
+
$ npx content-build format
|
|
198
|
+
warning: this repository declares no Prettier configuration, so `content-build format` applies the shared conventions while an editor and a bare `npx prettier` apply Prettier's own to the same tree; declare them in a prettier.config.mjs — export { default } from "@heroiclands/package-build/prettier";
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Any filename Prettier resolves works, `prettier.config.js` included; this is the
|
|
202
|
+
one every repository in the family carries.
|
|
203
|
+
|
|
204
|
+
## Step 4 — `package-build.config.yaml`
|
|
205
|
+
|
|
206
|
+
The build's declaration. This is the smallest one that compiles a module content
|
|
207
|
+
package:
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
contentPackage: bestiary
|
|
211
|
+
packageKind: modules
|
|
212
|
+
|
|
213
|
+
compatibility:
|
|
214
|
+
minimum: "14.359"
|
|
215
|
+
verified: "14.364"
|
|
216
|
+
|
|
217
|
+
stats:
|
|
218
|
+
lastModifiedBy: acmebuilder00000
|
|
219
|
+
|
|
220
|
+
packs:
|
|
221
|
+
- { name: journals, label: Journals, type: JournalEntry }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Five keys, and each answers a question the build cannot answer for itself:
|
|
225
|
+
|
|
226
|
+
- **`contentPackage`** names the content package this repository single-sources
|
|
227
|
+
— `bestiary` here, as `thalorna` and `kethira` name theirs. It is what a note's
|
|
228
|
+
canonical address is scoped by and what the published content index is named
|
|
229
|
+
for. **A note never declares its own package**; it belongs to this one, and a
|
|
230
|
+
`package:` key in a note is a hard error.
|
|
231
|
+
- **`packageKind`** is `modules`, `systems` or `documentation`. It decides
|
|
232
|
+
whether the manifest is written as `module.json` or `system.json`, where the
|
|
233
|
+
served asset root is rooted, and where the stamped system version comes from.
|
|
234
|
+
- **`compatibility.minimum`** is the Foundry core version every compiled
|
|
235
|
+
document is stamped with, and `verified` is the version the package is tested
|
|
236
|
+
against. There is no default: a guessed floor is stamped into every document
|
|
237
|
+
in the pack and stays invisible until something migrates on it.
|
|
238
|
+
- **`stats.lastModifiedBy`** is the sixteen-character Foundry user id stamped
|
|
239
|
+
into every document's `_stats` block.
|
|
240
|
+
- **`packs`** is the compendium list, and it is the _only_ place packs are
|
|
241
|
+
declared — the manifest's `packs` array is derived from it, so the two cannot
|
|
242
|
+
disagree.
|
|
243
|
+
|
|
244
|
+
Everything omitted has a default, and the defaults are the conventional
|
|
245
|
+
HeroicLands layout: the content tree at `assets/content`, the compiled packs at
|
|
246
|
+
`build/stage/packs`, the intermediates and caches elsewhere under `build/`.
|
|
247
|
+
[`configuration.md`](configuration.md) documents all eighteen top-level keys,
|
|
248
|
+
what each refusal message means, and the five values that are derived rather
|
|
249
|
+
than authored.
|
|
250
|
+
|
|
251
|
+
**How it worked:** run a content command and watch the complaint move from the
|
|
252
|
+
configuration to the content tree.
|
|
253
|
+
|
|
254
|
+
```console
|
|
255
|
+
$ npx content-build package compile
|
|
256
|
+
[ERROR]: Content tree not found at /path/to/acme-bestiary/assets/content.
|
|
257
|
+
[ERROR]: Pack JSON generation reported 1 error(s); refusing to compile packs from incomplete output.
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The configuration validated. There is simply nothing to compile yet.
|
|
261
|
+
|
|
262
|
+
## Step 5 — the content tree
|
|
263
|
+
|
|
264
|
+
Two notes: the package homepage, and one piece of content.
|
|
265
|
+
|
|
266
|
+
**Every package's tree holds exactly one `type: homepage` note.** It is the
|
|
267
|
+
package's front page, authored rather than generated, and `content-build lint`
|
|
268
|
+
requires it. It compiles to a page and to no Foundry document.
|
|
269
|
+
|
|
270
|
+
`assets/content/homepage.md`:
|
|
271
|
+
|
|
272
|
+
```markdown
|
|
273
|
+
---
|
|
274
|
+
type: homepage
|
|
275
|
+
shortcode: root
|
|
276
|
+
title: The Acme Bestiary
|
|
277
|
+
description: Creatures of the reed flats, their habits and their hides.
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
# The Acme Bestiary
|
|
281
|
+
|
|
282
|
+
What lives in the reed flats, what it eats, and what it is worth to the people
|
|
283
|
+
who hunt it.
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`assets/content/Bestiary/Marsh_Drake.md`:
|
|
287
|
+
|
|
288
|
+
```markdown
|
|
289
|
+
---
|
|
290
|
+
type: lore
|
|
291
|
+
subType: bestiary
|
|
292
|
+
name:
|
|
293
|
+
full: Marsh Drake
|
|
294
|
+
shortcode: marshdrake
|
|
295
|
+
description: A wingless drake of the reed flats, hunted for its hide and feared for its patience.
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
# Marsh Drake
|
|
299
|
+
|
|
300
|
+
The marsh drake is a wingless reptile of the reed flats, grown to the length of
|
|
301
|
+
a river barge. It hunts by stillness: it lies half-submerged for a day at a
|
|
302
|
+
time and takes whatever wades within reach.
|
|
303
|
+
|
|
304
|
+
## Habits
|
|
305
|
+
|
|
306
|
+
Drakes hold a stretch of water and defend it against their own kind. A stretch
|
|
307
|
+
that falls vacant is claimed within a season.
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Four things about that frontmatter are the note format in miniature, and they
|
|
311
|
+
are worth reading closely because everything else in the tree is a variation on
|
|
312
|
+
them.
|
|
313
|
+
|
|
314
|
+
**`type:` routes the note, not its location.** `Bestiary/` is a folder for a
|
|
315
|
+
human's benefit. What makes this note a journal is `type: lore`; a note's
|
|
316
|
+
directory has no bearing on which pack it lands in. `subType:` narrows it —
|
|
317
|
+
`bestiary` is "a kind of creature that is not a people" — and the pair
|
|
318
|
+
`(type, subType)` is what each game system maps onto its own document type.
|
|
319
|
+
|
|
320
|
+
**`shortcode:` is the note's address.** Cross-references are written
|
|
321
|
+
`[[lore-marshdrake]]` — the type and the shortcode — and resolve to whatever
|
|
322
|
+
that note compiles into, in whatever system is being compiled. A shortcode is
|
|
323
|
+
identity: renaming one breaks every link into it.
|
|
324
|
+
|
|
325
|
+
**`name.full` is the document name and the published URL**, derived by one
|
|
326
|
+
shared rule. There is no authored slug anywhere in this toolchain.
|
|
327
|
+
|
|
328
|
+
**Frontmatter has three regions and only one of them is open.** Top-level keys
|
|
329
|
+
(`type`, `shortcode`, `description`, `tags`, and anything else) are copied into
|
|
330
|
+
the generated web page, so an unrecognised one is a theme parameter rather than
|
|
331
|
+
an error. A `data:` block and a `sohl:` / `hm3:` block are closed: a misspelled
|
|
332
|
+
key there is a finding that names the key you meant.
|
|
333
|
+
[`content-format.md`](content-format.md) is the specification.
|
|
334
|
+
|
|
335
|
+
## Step 6 — check the tree
|
|
336
|
+
|
|
337
|
+
Four checks, and they are quick enough to run continuously while authoring.
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
npx content-build lint # addresses and frontmatter
|
|
341
|
+
npx content-build links # every wikilink resolves
|
|
342
|
+
npx content-build format # the shared Prettier options
|
|
343
|
+
npx content-build markdown # the shared markdownlint rule set
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**How it worked:**
|
|
347
|
+
|
|
348
|
+
```console
|
|
349
|
+
$ npx content-build lint
|
|
350
|
+
[INFO]: Addresses and frontmatter are well-formed (2 address(es) across 2 note(s)).
|
|
351
|
+
|
|
352
|
+
$ npx content-build links
|
|
353
|
+
[INFO]: 2 notes: every link is a labelled address, every anchor link lands and every address resolves (0 cross-package reference(s) via manifest), no wikilink in frontmatter, every homepage address resolvable.
|
|
354
|
+
|
|
355
|
+
$ npx content-build markdown
|
|
356
|
+
[INFO]: Markdown is clean.
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
`content-build format` reports rather than fixes, which is what a CI gate wants;
|
|
360
|
+
`--write` is the fixing form. Transcribe a file's whitespace by hand and the
|
|
361
|
+
reporting form names it:
|
|
362
|
+
|
|
363
|
+
```console
|
|
364
|
+
$ npx content-build format
|
|
365
|
+
package-build.config.yaml: error: is not formatted; run `content-build format --write` to fix it
|
|
366
|
+
[ERROR]: 1 of 6 file(s) are not formatted.
|
|
367
|
+
|
|
368
|
+
$ npx content-build format --write
|
|
369
|
+
[INFO]: Formatted 1 of 6 file(s).
|
|
370
|
+
|
|
371
|
+
$ npx content-build format
|
|
372
|
+
[INFO]: Formatting is clean (6 file(s)).
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Both forms walk the whole repository, not only the content tree — the
|
|
376
|
+
configuration, the scripts and the prose are all held to the same options.
|
|
377
|
+
|
|
378
|
+
Every finding these commands emit starts with the path of the file that is
|
|
379
|
+
wrong, followed by a line, a column, a severity and a message — the contract
|
|
380
|
+
[`diagnostics.md`](diagnostics.md) describes. A field that cannot be known is
|
|
381
|
+
dropped rather than guessed, so a finding about a whole file names only the file.
|
|
382
|
+
|
|
383
|
+
Forget the homepage and `content-build lint` says so by name:
|
|
384
|
+
|
|
385
|
+
```console
|
|
386
|
+
$ npx content-build lint
|
|
387
|
+
assets/content: error: holds no `type: homepage` note, so package "bestiary" publishes nothing at its own address /bestiary/ — a package's front page is one authored note in this tree, routed by `type:` rather than by filename
|
|
388
|
+
[ERROR]: 1 finding(s) across 1 note(s).
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Step 7 — compile the packs
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
npx content-build package compile
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```console
|
|
398
|
+
[INFO]: Content tree: 2 note(s) at /path/to/acme-bestiary/assets/content
|
|
399
|
+
[INFO]: Pack journals: /path/to/acme-bestiary/assets/content → /path/to/acme-bestiary/build/packs-json/journals
|
|
400
|
+
[INFO]: Compiled 1 journal entry (0 documentation entries)
|
|
401
|
+
[INFO]: Pack journals: compiling to LevelDB at /path/to/acme-bestiary/build/stage/packs/journals
|
|
402
|
+
[INFO]: Pack compilation complete.
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Two notes, one journal entry: the homepage compiles to a page and to no Foundry
|
|
406
|
+
document, which is why the counts differ.
|
|
407
|
+
|
|
408
|
+
The compile runs in two passes, and the intermediate is worth knowing about
|
|
409
|
+
because it is where you look when a document is not what you expected.
|
|
410
|
+
`build/packs-json/journals/Marsh_Drake_72d497c6a8e57f57.json` is the compiled
|
|
411
|
+
document as JSON, before it is written into the LevelDB pack — shown here with
|
|
412
|
+
the page's HTML elided, and otherwise entire:
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"name": "Marsh Drake",
|
|
417
|
+
"pages": [
|
|
418
|
+
{
|
|
419
|
+
"_id": "2ecce29fd1c5578f",
|
|
420
|
+
"name": "Marsh Drake",
|
|
421
|
+
"type": "text",
|
|
422
|
+
"title": { "show": true, "level": 1 },
|
|
423
|
+
"text": { "format": 1, "content": "…" },
|
|
424
|
+
"_key": "!journal.pages!72d497c6a8e57f57.2ecce29fd1c5578f"
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
"folder": null,
|
|
428
|
+
"sort": 0,
|
|
429
|
+
"ownership": { "default": 0 },
|
|
430
|
+
"flags": {},
|
|
431
|
+
"_id": "72d497c6a8e57f57",
|
|
432
|
+
"_stats": {
|
|
433
|
+
"systemId": null,
|
|
434
|
+
"systemVersion": null,
|
|
435
|
+
"coreVersion": "14.359",
|
|
436
|
+
"createdTime": 0,
|
|
437
|
+
"modifiedTime": 0,
|
|
438
|
+
"lastModifiedBy": "acmebuilder00000"
|
|
439
|
+
},
|
|
440
|
+
"_key": "!journal!72d497c6a8e57f57"
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
The body has become one text page, because a journal's pages are cut at its `#`
|
|
445
|
+
headings: this note has one, so the whole body is one page named for it. A
|
|
446
|
+
second `#` would produce a second page; anything before the first becomes a
|
|
447
|
+
leading page named "Introduction". A heading at any level carrying an
|
|
448
|
+
`{#anchor}` suffix also starts a page, because a Foundry UUID can address a page
|
|
449
|
+
and nothing smaller — which is how a link to a section inside a note resolves at
|
|
450
|
+
all. `_key` is the LevelDB key the entry is stored under, which is why the
|
|
451
|
+
intermediate carries it.
|
|
452
|
+
|
|
453
|
+
`coreVersion` is the `compatibility.minimum` from step 4 and `lastModifiedBy` is
|
|
454
|
+
the `stats.lastModifiedBy`. `systemId` and `systemVersion` are `null` because
|
|
455
|
+
this module declares no game system: it ships journals, which every system can
|
|
456
|
+
read. A module shipping Actors or Items declares the system it ships for, and
|
|
457
|
+
those two fields are stamped from it — see
|
|
458
|
+
[`systems`](configuration.md#systems).
|
|
459
|
+
|
|
460
|
+
`_id` is derived from the note's canonical address, so it is stable across
|
|
461
|
+
builds: recompiling does not renumber anything, and a world that imported
|
|
462
|
+
yesterday's pack still resolves against today's.
|
|
463
|
+
|
|
464
|
+
**A build that exits 0 and produces the wrong output is a bug.** The compile is
|
|
465
|
+
the stage where that matters most, so read the counts: "2 note(s)" and "Compiled
|
|
466
|
+
1 journal entry" are the two numbers that say what the tree held and what came
|
|
467
|
+
out of it.
|
|
468
|
+
|
|
469
|
+
### Cross-references, and what they compile into
|
|
470
|
+
|
|
471
|
+
Add a second note and link to it. `assets/content/Bestiary/Fen_Adder.md`:
|
|
472
|
+
|
|
473
|
+
```markdown
|
|
474
|
+
---
|
|
475
|
+
type: lore
|
|
476
|
+
subType: bestiary
|
|
477
|
+
name:
|
|
478
|
+
full: Fen Adder
|
|
479
|
+
shortcode: fenadder
|
|
480
|
+
description: A small venomous snake of the standing water, more feared than the drake.
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
# Fen Adder
|
|
484
|
+
|
|
485
|
+
A hand-long snake that lies under the surface scum. Its venom kills slowly and
|
|
486
|
+
reliably, which is why the reed-cutters fear it more than the drake.
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Then, in `Marsh_Drake.md`:
|
|
490
|
+
|
|
491
|
+
```markdown
|
|
492
|
+
Drakes hold a stretch of water and defend it against their own kind. A stretch
|
|
493
|
+
that falls vacant is claimed within a season. Reed-cutters working a drake's
|
|
494
|
+
water fear the [[lore-fenadder|fen adder]] more.
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```console
|
|
498
|
+
$ npx content-build links
|
|
499
|
+
[INFO]: 3 notes: every link is a labelled address, every anchor link lands and every address resolves (0 cross-package reference(s) via manifest), no wikilink in frontmatter, every homepage address resolvable.
|
|
500
|
+
|
|
501
|
+
$ npx content-build package compile
|
|
502
|
+
[INFO]: Compiled 2 journal entries (0 documentation entries)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
And in the compiled journal, the wikilink has become a Foundry reference:
|
|
506
|
+
|
|
507
|
+
```text
|
|
508
|
+
@UUID[Compendium.acme-bestiary.journals.JournalEntry.c7c3488c3e2a9282]{fen adder}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
That is the whole point of addressing notes by shortcode rather than by
|
|
512
|
+
filename or by id: one authored link, resolved at build time into the reference
|
|
513
|
+
each surface needs — a `@UUID` in a compendium journal, a relative URL on the
|
|
514
|
+
website. A link that resolves to nothing is an error naming the note, never a
|
|
515
|
+
blank.
|
|
516
|
+
|
|
517
|
+
## Step 8 — what the package ships
|
|
518
|
+
|
|
519
|
+
The packs are compiled but the package is not yet assembled. A Foundry package
|
|
520
|
+
is a staged directory — a manifest, the packs, and whatever files the manifest
|
|
521
|
+
points at — and the configuration declares both halves.
|
|
522
|
+
|
|
523
|
+
Write the three files the manifest will name:
|
|
524
|
+
|
|
525
|
+
`README.md`:
|
|
526
|
+
|
|
527
|
+
```markdown
|
|
528
|
+
# Acme Bestiary
|
|
529
|
+
|
|
530
|
+
Creatures of the reed flats, as compendium journals for Foundry VTT.
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
`LICENSE.md`:
|
|
534
|
+
|
|
535
|
+
```markdown
|
|
536
|
+
# License
|
|
537
|
+
|
|
538
|
+
Code: GPL-3.0-or-later. Content: CC-BY-SA-4.0.
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
`lang/en.json`:
|
|
542
|
+
|
|
543
|
+
```json
|
|
544
|
+
{
|
|
545
|
+
"ACMEBESTIARY": {
|
|
546
|
+
"Title": "Acme Bestiary"
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Then add the `packageBuild:` section to `package-build.config.yaml`. It is the
|
|
552
|
+
packaging half's reserved section — validated separately from everything above
|
|
553
|
+
it, and documented under
|
|
554
|
+
[the `packageBuild` section](configuration.md#the-packagebuild-section):
|
|
555
|
+
|
|
556
|
+
```yaml
|
|
557
|
+
packageBuild:
|
|
558
|
+
assets:
|
|
559
|
+
- { from: README.md, to: README.md }
|
|
560
|
+
- { from: LICENSE.md, to: LICENSE.md }
|
|
561
|
+
- { from: lang, to: lang }
|
|
562
|
+
|
|
563
|
+
manifest:
|
|
564
|
+
title: Acme Bestiary
|
|
565
|
+
description: Creatures of the reed flats, as compendium journals.
|
|
566
|
+
authors:
|
|
567
|
+
- name: Acme
|
|
568
|
+
license: LICENSE.md
|
|
569
|
+
readme: README.md
|
|
570
|
+
languages:
|
|
571
|
+
- lang: en
|
|
572
|
+
name: English
|
|
573
|
+
path: lang/en.json
|
|
574
|
+
packFolders:
|
|
575
|
+
- name: Acme Bestiary
|
|
576
|
+
sorting: m
|
|
577
|
+
packs:
|
|
578
|
+
- journals
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
`assets:` is a copy list, from the repository into the stage. `manifest:` is
|
|
582
|
+
everything about the manifest that cannot be derived from `package.json` or from
|
|
583
|
+
the keys above — the human-facing identity, and the files Foundry links to.
|
|
584
|
+
`title` is required by Foundry and has no derivation; supply it.
|
|
585
|
+
|
|
586
|
+
`packFolders` may name only packs this package ships, and the build compares the
|
|
587
|
+
two lists rather than trusting them to agree:
|
|
588
|
+
|
|
589
|
+
```console
|
|
590
|
+
$ npx package-build manifest
|
|
591
|
+
package-build.config.yaml:36:21: error: packFolders: folder "Acme Bestiary" names pack "items", which this package does not ship (packs: journals)
|
|
592
|
+
package-build: packFolders names 1 pack this package does not ship (reported above). Foundry skips a name it cannot resolve, so the folder would ship missing those packs — correct `packageBuild.manifest.packFolders`.
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
That is the shape of the checks throughout: Foundry's own behaviour on bad input
|
|
596
|
+
is to skip it silently, so the build refuses to emit input Foundry would skip.
|
|
597
|
+
|
|
598
|
+
Stage the files:
|
|
599
|
+
|
|
600
|
+
```console
|
|
601
|
+
$ npx package-build assets
|
|
602
|
+
✅ Static assets staged (3 entries, 3 files).
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Name a path that is not there and it says so before copying anything:
|
|
606
|
+
|
|
607
|
+
```console
|
|
608
|
+
$ npx package-build assets
|
|
609
|
+
package-build: Cannot stage assets — these paths do not exist:
|
|
610
|
+
README.md
|
|
611
|
+
LICENSE.md
|
|
612
|
+
lang
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## Step 9 — generate the manifest
|
|
616
|
+
|
|
617
|
+
```console
|
|
618
|
+
$ npx package-build manifest
|
|
619
|
+
✅ Wrote build/stage/module.json (16 keys, 1 packs).
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
`build/stage/module.json` is the file Foundry installs:
|
|
623
|
+
|
|
624
|
+
```json
|
|
625
|
+
{
|
|
626
|
+
"id": "acme-bestiary",
|
|
627
|
+
"title": "Acme Bestiary",
|
|
628
|
+
"description": "Creatures of the reed flats, as compendium journals.",
|
|
629
|
+
"version": "0.1.0",
|
|
630
|
+
"authors": [{ "name": "Acme" }],
|
|
631
|
+
"license": "LICENSE.md",
|
|
632
|
+
"readme": "README.md",
|
|
633
|
+
"flags": {
|
|
634
|
+
"metadataUrl": "https://github.com/HeroicLands/acme-bestiary/releases/download/v0.1.0/bestiary-metadata.jsonl"
|
|
635
|
+
},
|
|
636
|
+
"compatibility": { "minimum": "14.359", "verified": "14.364" },
|
|
637
|
+
"languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }],
|
|
638
|
+
"packFolders": [{ "name": "Acme Bestiary", "sorting": "m", "packs": ["journals"] }],
|
|
639
|
+
"packs": [
|
|
640
|
+
{
|
|
641
|
+
"label": "Journals",
|
|
642
|
+
"type": "JournalEntry",
|
|
643
|
+
"name": "journals",
|
|
644
|
+
"path": "packs/journals",
|
|
645
|
+
"private": false
|
|
646
|
+
}
|
|
647
|
+
],
|
|
648
|
+
"url": "https://github.com/HeroicLands/acme-bestiary",
|
|
649
|
+
"bugs": "https://github.com/HeroicLands/acme-bestiary/issues",
|
|
650
|
+
"manifest": "https://github.com/HeroicLands/acme-bestiary/releases/latest/download/module.json",
|
|
651
|
+
"download": "https://github.com/HeroicLands/acme-bestiary/releases/download/v0.1.0/module.zip"
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Read it against what you authored, because the derivations are the part worth
|
|
656
|
+
checking:
|
|
657
|
+
|
|
658
|
+
- `id` is `package.json`'s `name`, verbatim.
|
|
659
|
+
- `version` and the pinned `download` address are its `version`.
|
|
660
|
+
- `url`, `bugs`, `manifest` and `download` are all built from its
|
|
661
|
+
`repository.url`. Omit that and the command refuses rather than writing an
|
|
662
|
+
unpublishable manifest:
|
|
663
|
+
|
|
664
|
+
```console
|
|
665
|
+
$ npx package-build manifest
|
|
666
|
+
package-build: package.json declares no `repository.url`, so the manifest has no release addresses to advertise. Add it.
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
- `packs` is the `packs:` list from step 4, with `path` and `private` filled in.
|
|
670
|
+
It is never declared twice.
|
|
671
|
+
- `manifest` points at `releases/latest`, while `download` and `flags.metadataUrl`
|
|
672
|
+
are pinned to this version. That asymmetry is deliberate: the manifest address
|
|
673
|
+
must keep resolving as new releases land, and the download must not move under
|
|
674
|
+
a world that installed it.
|
|
675
|
+
|
|
676
|
+
## Step 10 — publish the content index
|
|
677
|
+
|
|
678
|
+
```console
|
|
679
|
+
$ npx content-build content-index
|
|
680
|
+
[INFO]: bestiary → build/content-index/bestiary-metadata.jsonl (3 notes, 1 KiB)
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
This is the file `flags.metadataUrl` advertises, and it is how packages address
|
|
684
|
+
each other. One JSON object per note, carrying the note's canonical address, its
|
|
685
|
+
name, its description and the Foundry UUID it compiles to:
|
|
686
|
+
|
|
687
|
+
```text
|
|
688
|
+
{"address":{"canonical":"bestiary-none-lore-marshdrake","slug":"lore-marshdrake"},"aliasesAscii":[],"anchors":[],"description":"A wingless drake of the reed flats, hunted for its hide and feared for its patience.","documentation":null,"file":{"folder":"Bestiary","name":"Marsh_Drake","path":"Bestiary/Marsh_Drake.md"},"foundry":{"none":{"uuid":"Compendium.acme-bestiary.journals.JournalEntry.72d497c6a8e57f57"}},"id":"72d497c6a8e57f57","name":{"full":"Marsh Drake"},"nameAscii":"Marsh Drake","package":"bestiary","shortcode":"marshdrake","subType":"bestiary","type":"lore"}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
A downstream package that declares a dependency on this one fetches this file
|
|
692
|
+
for the release it pins, and `[[lore-marshdrake]]` written in _its_ tree resolves
|
|
693
|
+
through it to that UUID. The homepage's entry carries `"foundry": null` — it
|
|
694
|
+
compiles to a page, so there is no document to address.
|
|
695
|
+
|
|
696
|
+
The index is derived and disposable; it is written under `build/` and rebuilt
|
|
697
|
+
from the tree every time.
|
|
698
|
+
|
|
699
|
+
## Step 11 — the release archive
|
|
700
|
+
|
|
701
|
+
```console
|
|
702
|
+
$ npx package-build release
|
|
703
|
+
✅ Packaged 0.1.0 for release: build/dist/module.zip (0.0 MB)
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
`build/dist/` then holds the three files a GitHub release needs, and the archive
|
|
707
|
+
is named from `packageKind` — a module ships `module.zip`, a system
|
|
708
|
+
`system.zip` — so no repository states it a second time:
|
|
709
|
+
|
|
710
|
+
```console
|
|
711
|
+
$ ls build/dist
|
|
712
|
+
bestiary-metadata.jsonl
|
|
713
|
+
module.json
|
|
714
|
+
module.zip
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
The manifest and the content index sit beside the archive rather than only
|
|
718
|
+
inside it, because that is where the addresses in the manifest point: a
|
|
719
|
+
consumer reads `module.json` from the release without downloading the zip, and
|
|
720
|
+
then fetches the index it names.
|
|
721
|
+
|
|
722
|
+
The archive itself is `build/stage/` verbatim — the manifest at the root, the
|
|
723
|
+
LevelDB packs under `packs/`, and the files the assets step put there. `unzip
|
|
724
|
+
-l build/dist/module.zip` is worth reading once, because it is the only view of
|
|
725
|
+
what a player actually installs.
|
|
726
|
+
|
|
727
|
+
That is the whole build. From an empty directory: identity, configuration,
|
|
728
|
+
notes, checks, packs, stage, manifest, index, archive.
|
|
729
|
+
|
|
730
|
+
## Putting it in order
|
|
731
|
+
|
|
732
|
+
The eleven steps above are the order you need them the _first_ time. Thereafter
|
|
733
|
+
the build is one chain, and this is the order it runs in:
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
npx content-build format # the prose checks
|
|
737
|
+
npx content-build markdown
|
|
738
|
+
npx content-build lint # the content checks
|
|
739
|
+
npx content-build links
|
|
740
|
+
npx content-build content-index # the index the manifest advertises
|
|
741
|
+
npx package-build assets # the stage: the shipped files
|
|
742
|
+
npx content-build package compile
|
|
743
|
+
npx package-build manifest # the stage: the manifest
|
|
744
|
+
npx package-build release # the archive
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
Only one of those orderings is enforced, and it is worth knowing which.
|
|
748
|
+
`package-build release` refuses to pack an archive whose manifest advertises a
|
|
749
|
+
content index the tree never wrote:
|
|
750
|
+
|
|
751
|
+
```console
|
|
752
|
+
$ npx package-build release
|
|
753
|
+
package-build: the manifest advertises bestiary-metadata.jsonl as `flags.metadataUrl` but no such file exists — looked in …/build/stage/bestiary-metadata.jsonl and …/build/content-index/bestiary-metadata.jsonl. Build the content index before packing the release.
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
The rest of the chain is order-independent: `assets`, `compile` and `manifest`
|
|
757
|
+
each write their own part of `build/stage/` and none clobbers another's. They
|
|
758
|
+
are written in this order because it is the order that reads as an assembly —
|
|
759
|
+
the files, then the packs, then the manifest describing both — and because
|
|
760
|
+
everything that reads the tree runs before everything that writes the stage.
|
|
761
|
+
|
|
762
|
+
[`project-setup.md`](project-setup.md) turns this into npm scripts, and covers
|
|
763
|
+
everything a repository carries beyond the build itself: the git hooks, the
|
|
764
|
+
changeset directory, the label registry, and what each script in the chain is
|
|
765
|
+
for.
|
|
766
|
+
|
|
767
|
+
## What comes next
|
|
768
|
+
|
|
769
|
+
Four capabilities are configuration away, and each has its own guide material.
|
|
770
|
+
None of them is needed to build a package.
|
|
771
|
+
|
|
772
|
+
**A website.** Set `publish.site: content` and add a `site:` block naming the
|
|
773
|
+
sections, and `content-build site` writes a Hugo content tree from the same
|
|
774
|
+
notes. The site renders through `@heroiclands/hugo-theme`, and the package
|
|
775
|
+
publishes at `https://www.heroiclands.org/<contentPackage>/`.
|
|
776
|
+
|
|
777
|
+
**Another package's content.** Declare a dependency under `relationships`, and
|
|
778
|
+
`content-build deps fetch` caches that release's published content index so
|
|
779
|
+
`[[…]]` links into it resolve. A relationship marked `itemCatalog: true` also
|
|
780
|
+
caches the release's Item packs, which is what lets a being embed items by
|
|
781
|
+
`(type, shortcode)`. Fetching never happens during a compile: a cold cache
|
|
782
|
+
fails naming `deps fetch` rather than reaching the network.
|
|
783
|
+
|
|
784
|
+
**A game system's documents.** A module shipping Actors or Items names its
|
|
785
|
+
`itemBuilders` registry — `sohl` or `hm3` — and declares the system under
|
|
786
|
+
`systems:`. That is what maps a note's `(type, subType)` onto a system's own
|
|
787
|
+
document type, and what supplies the `_stats.systemVersion` every document is
|
|
788
|
+
stamped with.
|
|
789
|
+
|
|
790
|
+
**Deploying and testing.** `package-build deploy <stage>` pushes a staged
|
|
791
|
+
package to a Foundry data directory or a remote host; `package-build container`
|
|
792
|
+
runs a licensed Foundry in Docker; `package-build e2e` drives the Cypress suite
|
|
793
|
+
against it. All three need a Foundry install, credentials, or both — see
|
|
794
|
+
[`commands.md`](commands.md).
|
|
795
|
+
|
|
796
|
+
## A documentation package
|
|
797
|
+
|
|
798
|
+
`packageKind: documentation` is the third kind, and it is a different shape
|
|
799
|
+
rather than a smaller one: a package that publishes a content tree as a website
|
|
800
|
+
and a book, and compiles no Foundry documents at all.
|
|
801
|
+
|
|
802
|
+
Everything in steps 4 and 8 that exists to describe a Foundry package is
|
|
803
|
+
**refused** there rather than ignored, each with a message saying why:
|
|
804
|
+
`foundryPackage`, `stats`, `packs`, `itemBuilders`, `compatibility`,
|
|
805
|
+
`relationships`, `systems`, `requiresSystem` and `docs`. In exchange, `publish`
|
|
806
|
+
becomes required, with `site: content` — publishing the tree is the whole of
|
|
807
|
+
what the package does.
|
|
808
|
+
|
|
809
|
+
So a documentation package's configuration is steps 1 through 6 with a different
|
|
810
|
+
`packageKind`, plus a `site:` block, and then `content-build site` in place of
|
|
811
|
+
steps 7 through 11. [`configuration.md`](configuration.md) carries the refusal
|
|
812
|
+
message for every key; [`commands.md`](commands.md) covers the two commands such
|
|
813
|
+
a package lives on, `content-build site` and `content-build pdf`.
|