@heroiclands/package-build 19.0.0 → 20.2.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 +1100 -0
- package/CONTENT.md +264 -33
- package/README.md +43 -4
- package/bin/content-build.mjs +94 -3
- package/config.mjs +9 -1
- package/content-config.mjs +99 -19
- package/docs/content-format.md +394 -72
- package/e2e.mjs +297 -3
- package/engine/actor-compiler.mjs +197 -7
- package/engine/address-charset.mjs +23 -5
- package/engine/base-compiler.mjs +63 -2
- package/engine/bundles.mjs +9 -0
- package/engine/content-address.mjs +92 -1
- package/engine/content-charset.mjs +434 -0
- package/engine/content-format.mjs +102 -0
- package/engine/content-icons.mjs +388 -0
- package/engine/content-index.mjs +11 -8
- package/engine/content-links.mjs +37 -21
- package/engine/field-reference.mjs +57 -5
- package/engine/field-spec.mjs +214 -7
- package/engine/folder-notes.mjs +24 -1
- package/engine/foreign-catalog.mjs +112 -7
- package/engine/foundry-entries.mjs +14 -0
- package/engine/frontmatter-lint.mjs +377 -56
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +72 -12
- package/engine/helpers.mjs +96 -10
- package/engine/index.mjs +9 -0
- package/engine/item-compiler.mjs +37 -0
- package/engine/journals.mjs +21 -4
- package/engine/macros.mjs +8 -0
- package/engine/map-notes.mjs +7 -7
- package/engine/note-claims.mjs +208 -5
- package/engine/note-ids.mjs +25 -1
- package/engine/note-vocabulary.mjs +76 -9
- package/engine/pack-config.mjs +102 -12
- package/engine/pack-router.mjs +0 -0
- package/engine/prose-config.mjs +42 -0
- package/engine/prose-lint.mjs +126 -0
- package/engine/retired-fields.mjs +57 -16
- package/engine/runtime-only-fields.mjs +204 -0
- package/engine/scenes.mjs +12 -19
- package/engine/schema-check.mjs +23 -1
- package/engine/schema-extract.mjs +13 -0
- package/engine/site-index.mjs +17 -0
- package/engine/subtype-registry.mjs +30 -0
- package/engine/system-block.mjs +81 -3
- package/engine/web-wikilinks.mjs +33 -27
- package/engine/wikilink-syntax.mjs +7 -0
- package/engine/wikilinks.mjs +74 -16
- package/hm3/actors.mjs +63 -13
- package/package.json +2 -2
- package/sohl/actors.mjs +106 -7
- package/sohl/item-fields.mjs +203 -0
- package/sohl/note-schemas.mjs +6 -3
- package/types/config.d.mts +7 -0
- package/types/e2e.d.mts +130 -3
- package/types/engine/actor-compiler.d.mts +83 -3
- package/types/engine/address-charset.d.mts +22 -4
- package/types/engine/base-compiler.d.mts +54 -3
- package/types/engine/content-address.d.mts +64 -0
- package/types/engine/content-charset.d.mts +127 -0
- package/types/engine/content-format.d.mts +9 -0
- package/types/engine/content-icons.d.mts +151 -0
- package/types/engine/field-spec.d.mts +271 -3
- package/types/engine/folder-notes.d.mts +20 -0
- package/types/engine/foreign-catalog.d.mts +38 -2
- package/types/engine/foundry-entries.d.mts +6 -0
- package/types/engine/frontmatter-lint.d.mts +164 -30
- package/types/engine/frontmatter.d.mts +11 -11
- package/types/engine/generate.d.mts +27 -0
- package/types/engine/helpers.d.mts +45 -9
- package/types/engine/index.d.mts +3 -0
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/note-claims.d.mts +67 -0
- package/types/engine/note-ids.d.mts +14 -0
- package/types/engine/pack-config.d.mts +35 -0
- package/types/engine/prose-config.d.mts +41 -0
- package/types/engine/prose-lint.d.mts +36 -0
- package/types/engine/retired-fields.d.mts +29 -13
- package/types/engine/runtime-only-fields.d.mts +102 -0
- package/types/engine/schema-check.d.mts +10 -1
- package/types/engine/subtype-registry.d.mts +21 -0
- package/types/engine/system-block.d.mts +28 -2
- package/types/sohl/actors.d.mts +3 -3
package/engine/generate.mjs
CHANGED
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
* Each `*` compiler walks the whole content tree and selects its own entries by
|
|
24
24
|
* the note's `type` — every note in the tree belongs to this repository's
|
|
25
25
|
* `contentPackage` (#56) — so routing is directory-agnostic: a file lands in a
|
|
26
|
-
* pack because of its `type`, not its location. Which packs exist
|
|
27
|
-
* folder hierarchy
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* pack because of its `type`, not its location. Which packs exist is declared
|
|
27
|
+
* in `package-build.config.yaml`; the **folder hierarchy is not declared
|
|
28
|
+
* anywhere**. A folder is a note like any other (#256), named by `packFolder`
|
|
29
|
+
* — its address — and it materialises in every pack that holds a document
|
|
30
|
+
* naming it, its ancestors with it (#257). So no pack loads a folder list, and
|
|
31
|
+
* two packs can no longer disagree about a folder they both hold.
|
|
30
32
|
*
|
|
31
33
|
* **The order the passes run in is derived, not declared** — see
|
|
32
34
|
* {@link orderPassesByDependency}. The declared list is the manifest's `packs`
|
|
@@ -66,7 +68,10 @@ import { buildCompileCorpus } from "./compile-corpus.mjs";
|
|
|
66
68
|
import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
67
69
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
68
70
|
import { routerFor } from "./pack-router.mjs";
|
|
69
|
-
import { unclaimedNoteFindings } from "./note-claims.mjs";
|
|
71
|
+
import { NEVER_PACKED_TYPES, unclaimedNoteFindings } from "./note-claims.mjs";
|
|
72
|
+
// Which document a content type compiles into, so the art declaration below is
|
|
73
|
+
// answered from the same routing the compile uses (#349).
|
|
74
|
+
import { RETIRED_TYPES, currentType, packForType } from "./ids.mjs";
|
|
70
75
|
import { contentPackage } from "./content-package.mjs";
|
|
71
76
|
|
|
72
77
|
/**
|
|
@@ -131,6 +136,56 @@ export function compilerFor(docType, system = null) {
|
|
|
131
136
|
return (system && SYSTEM_COMPILERS[system]?.[docType]) || COMPILERS[docType];
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The art fields a note of one content type reaches its document through, and
|
|
141
|
+
* the document it reaches (#349).
|
|
142
|
+
*
|
|
143
|
+
* **Derived, never listed.** A note's type routes to a document type
|
|
144
|
+
* ({@link packForType}), a document type routes to the pass that compiles it
|
|
145
|
+
* ({@link compilerFor}), and the pass declares which art it emits
|
|
146
|
+
* ({@link BasePackCompiler.emitsArt}). So the answer is assembled from the same
|
|
147
|
+
* three statements the compile itself follows, and a pass that starts or stops
|
|
148
|
+
* emitting art changes this by changing its own declaration. A second table of
|
|
149
|
+
* "types with no image" would be a table free to drift from what is emitted,
|
|
150
|
+
* which is the defect this exists to report rather than to reproduce.
|
|
151
|
+
*
|
|
152
|
+
* **The union across systems**, because a note is compiled by whichever pack
|
|
153
|
+
* claims it: a tree feeding both SoHL and HM3 has two Actor passes, and a field
|
|
154
|
+
* either of them emits is live for the note. Only a field *no* pass emits is
|
|
155
|
+
* inert, and that is the finding this supports.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} type - The note's content type.
|
|
158
|
+
* @returns {{document: string|null, art: readonly string[]}|null} What the type
|
|
159
|
+
* compiles into and the art it carries there, or `null` where no claim can be
|
|
160
|
+
* made — a retired type, which is reported as retired instead.
|
|
161
|
+
*/
|
|
162
|
+
export function emittedArtFor(type) {
|
|
163
|
+
const name = String(type ?? "");
|
|
164
|
+
if (!name || Object.hasOwn(RETIRED_TYPES, name)) return null;
|
|
165
|
+
|
|
166
|
+
// A homepage compiles into a *page*, not a compendium document, so nothing
|
|
167
|
+
// it authors reaches one. It is the one type whose absence from every pack
|
|
168
|
+
// is the intended state (`NEVER_PACKED_TYPES`).
|
|
169
|
+
if (NEVER_PACKED_TYPES.has(currentType(name))) return { document: null, art: [] };
|
|
170
|
+
|
|
171
|
+
// A folder reaches a pack by a route of its own — it materialises in every
|
|
172
|
+
// pack holding a document that references it — so `packForType` has no
|
|
173
|
+
// answer for it and no compiler class writes it. `folderDocument` does, and
|
|
174
|
+
// a Foundry `Folder` has no artwork at all.
|
|
175
|
+
if (currentType(name) === FOLDER_TYPE) return { document: "Folder", art: [] };
|
|
176
|
+
|
|
177
|
+
const { docType } = packForType(name);
|
|
178
|
+
const passes = [
|
|
179
|
+
COMPILERS[docType],
|
|
180
|
+
...Object.values(SYSTEM_COMPILERS).map((bySystem) => bySystem[docType]),
|
|
181
|
+
].filter(Boolean);
|
|
182
|
+
if (!passes.length) return null;
|
|
183
|
+
|
|
184
|
+
const art = new Set();
|
|
185
|
+
for (const pass of passes) for (const field of pass.emitsArt ?? []) art.add(field);
|
|
186
|
+
return { document: docType, art: Object.freeze([...art]) };
|
|
187
|
+
}
|
|
188
|
+
|
|
134
189
|
/**
|
|
135
190
|
* Root of the build-only JSON tree for one pack.
|
|
136
191
|
*
|
|
@@ -442,8 +497,11 @@ async function generatePack(
|
|
|
442
497
|
// not contain, for a repository that authors beings without
|
|
443
498
|
// holding the items they are assembled from. Cache-only: a cold
|
|
444
499
|
// cache throws naming `content-build deps fetch` rather than
|
|
445
|
-
// downloading inside a compile.
|
|
446
|
-
|
|
500
|
+
// downloading inside a compile. Scoped to this pack's system for the
|
|
501
|
+
// reason the local half is (#58): both halves answer one lookup, so a
|
|
502
|
+
// dependency shipping two systems' items would otherwise supply the
|
|
503
|
+
// wrong vocabulary's document for an address that exists in both.
|
|
504
|
+
foreignSourceDirs: foreignItemCatalogDirs(config, system ?? null),
|
|
447
505
|
// The bundles pass resolves each Adventure's members against the output
|
|
448
506
|
// of every pass that produces one. Stated from the configured pack list
|
|
449
507
|
// for the same reason `itemsSourceDirs` is (#1508), and scoped to this
|
|
@@ -656,11 +714,13 @@ export async function generatePacksJson({ only, config = loadPackConfig() } = {}
|
|
|
656
714
|
// which wins over the id it derives under the folder namespace.
|
|
657
715
|
// A record's `id` is not that: the index fills it in for every
|
|
658
716
|
// addressable note (#270), so handing records straight over
|
|
659
|
-
// would make every folder look pinned
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
//
|
|
663
|
-
//
|
|
717
|
+
// would make every folder look pinned. Since #310 the *value*
|
|
718
|
+
// would be right either way — the index derives a folder's id
|
|
719
|
+
// under the folder namespace, so the two agree — but `derivedId`
|
|
720
|
+
// would not, and it is what tells an author whether a duplicate
|
|
721
|
+
// id was two pins or two addresses. The index cannot tell a pin
|
|
722
|
+
// from a derivation, so the note is read — and only folder notes
|
|
723
|
+
// are, 79 of `sohl`'s 1,685 rather than all of them.
|
|
664
724
|
corpus.records
|
|
665
725
|
.filter(
|
|
666
726
|
(record) =>
|
package/engine/helpers.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import path from "path";
|
|
|
31
31
|
import yaml from "yaml";
|
|
32
32
|
import unidecode from "unidecode";
|
|
33
33
|
import markdownit from "markdown-it";
|
|
34
|
+
import { iconPlugin } from "./content-icons.mjs";
|
|
34
35
|
import log from "loglevel";
|
|
35
36
|
|
|
36
37
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
@@ -68,7 +69,15 @@ export {
|
|
|
68
69
|
parseValueDesc,
|
|
69
70
|
} from "./frontmatter.mjs";
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
/**
|
|
73
|
+
* The markdown renderer every surface shares.
|
|
74
|
+
*
|
|
75
|
+
* `html: true` is long-standing and load-bearing — notes carry raw blocks — and
|
|
76
|
+
* it is also why {@link module:engine/content-icons} exists rather than an
|
|
77
|
+
* instruction to write `<i class="fa-solid …">` by hand: that would render on
|
|
78
|
+
* the two HTML surfaces and be silently dropped by the third (#378).
|
|
79
|
+
*/
|
|
80
|
+
export const md = markdownit({ html: true }).use(iconPlugin());
|
|
72
81
|
|
|
73
82
|
/**
|
|
74
83
|
* Parses a markdown file with YAML frontmatter.
|
|
@@ -400,15 +409,92 @@ export function makeFilename(name, id) {
|
|
|
400
409
|
* non-alphanumerics collapsed to single hyphens.
|
|
401
410
|
*/
|
|
402
411
|
|
|
412
|
+
/**
|
|
413
|
+
* The path prefixes that name a package other than the one being compiled.
|
|
414
|
+
*
|
|
415
|
+
* Foundry serves every installed package from a root named for its kind, so a
|
|
416
|
+
* path opening with one of these is already a served address and belongs to
|
|
417
|
+
* somebody else — most often `systems/sohl/assets/…`, where every default this
|
|
418
|
+
* toolchain ships lives, and which a module's content cites as readily as the
|
|
419
|
+
* system's own does.
|
|
420
|
+
*
|
|
421
|
+
* **Two, not "the ones we happen to use".** `worlds/` is left out on purpose: a
|
|
422
|
+
* package may not ship art out of a world, so a note that writes one has made a
|
|
423
|
+
* mistake, and prefixing it yields a plainly broken path rather than a
|
|
424
|
+
* plausible one that fails silently much later.
|
|
425
|
+
*
|
|
426
|
+
* @type {readonly string[]}
|
|
427
|
+
*/
|
|
428
|
+
const FOREIGN_PACKAGE_ROOTS = Object.freeze(["systems/", "modules/"]);
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Whether a path already addresses something this package does not own, and so
|
|
432
|
+
* must be emitted exactly as authored.
|
|
433
|
+
*
|
|
434
|
+
* Three shapes qualify, each a different kind of "not mine":
|
|
435
|
+
*
|
|
436
|
+
* - **Another package** — `systems/…` or `modules/…`, per
|
|
437
|
+
* {@link FOREIGN_PACKAGE_ROOTS}.
|
|
438
|
+
* - **Somewhere off this install** — a URI scheme (`https:`, `data:`) or a
|
|
439
|
+
* protocol-relative `//cdn…`.
|
|
440
|
+
* - **The data root itself** — a leading `/`, which Foundry serves from the
|
|
441
|
+
* install rather than from any package.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} s - A non-empty authored path.
|
|
444
|
+
* @returns {boolean} Whether it passes through untranslated.
|
|
445
|
+
*/
|
|
446
|
+
function addressesAnotherPackage(s) {
|
|
447
|
+
if (FOREIGN_PACKAGE_ROOTS.some((root) => s.startsWith(root))) return true;
|
|
448
|
+
// `//host/x.png` — protocol-relative, so it leaves this origin entirely.
|
|
449
|
+
// Checked before the single-slash case, which would otherwise claim it.
|
|
450
|
+
if (s.startsWith("//")) return true;
|
|
451
|
+
// `/x.png` — rooted at the Foundry data root, not at any package.
|
|
452
|
+
if (s.startsWith("/")) return true;
|
|
453
|
+
// `https://…`, `data:…`, `file:…` — a scheme, so not a path at all.
|
|
454
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(s);
|
|
455
|
+
}
|
|
456
|
+
|
|
403
457
|
/**
|
|
404
458
|
* Translate a content-relative image path into its Foundry-relative form.
|
|
405
459
|
*
|
|
406
460
|
* Content frontmatter (`img` / `portrait`) authors a single path that has to
|
|
407
|
-
* work for Foundry, the knowledgebase, and the website.
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
461
|
+
* work for Foundry, the knowledgebase, and the website. **Its first segment
|
|
462
|
+
* says which package owns the file** (#331), and there are exactly three
|
|
463
|
+
* answers:
|
|
464
|
+
*
|
|
465
|
+
* | Authored path starts with | Owner | Emitted |
|
|
466
|
+
* | ------------------------- | --------------------- | -------------------- |
|
|
467
|
+
* | `systems/` | a separate **system** | unchanged |
|
|
468
|
+
* | `modules/` | a separate **module** | unchanged |
|
|
469
|
+
* | anything else | **this package** | `<assetRoot>/<path>` |
|
|
470
|
+
*
|
|
471
|
+
* So `icons/relic.svg` compiles to `systems/sohl/assets/icons/relic.svg` here
|
|
472
|
+
* and to `modules/sohl-thalorna/assets/icons/relic.svg` in a module — the asset
|
|
473
|
+
* root is derived from the configuration, and is the one place `systems/sohl`
|
|
474
|
+
* is ever spelled (#1508). An authored
|
|
475
|
+
* `systems/sohl/assets/icons/noun/shield.svg` is left exactly as written,
|
|
476
|
+
* whichever package is compiling it.
|
|
477
|
+
*
|
|
478
|
+
* **This is a rule about ownership, not an allowlist of directories.** It used
|
|
479
|
+
* to prefix `icons/…` and `images/…` and pass everything else through — the
|
|
480
|
+
* same answer for every path any tree authors today, and the wrong one for the
|
|
481
|
+
* next directory a package ships. `sohl-kethira-basic` keeps art under
|
|
482
|
+
* `assets/artwork/`, so an authored `artwork/deity.webp` would have shipped
|
|
483
|
+
* unprefixed: a 404 in Foundry, reported by nothing. That a package owns its
|
|
484
|
+
* own tree is the fact; the directory names inside it are that package's
|
|
485
|
+
* business (#331).
|
|
486
|
+
*
|
|
487
|
+
* **Off-install addresses pass through too**, which is the same rule rather
|
|
488
|
+
* than a fourth: a URL, a `data:` URI, or a `/`-rooted path names something no
|
|
489
|
+
* package owns. See {@link addressesAnotherPackage}.
|
|
490
|
+
*
|
|
491
|
+
* **`banner:` does not follow this rule, deliberately (#331).** It is not an
|
|
492
|
+
* asset path inside a Foundry install at all: it reaches no compiled document,
|
|
493
|
+
* and its only consumer is the Hugo theme, which prefixes a relative value with
|
|
494
|
+
* `images/` and joins it onto `params.cdnBaseURL`. The two fields look alike
|
|
495
|
+
* and address different places — `img:` a file Foundry serves, `banner:` a file
|
|
496
|
+
* the CDN serves — so they are documented apart rather than reconciled into one
|
|
497
|
+
* rule that would be true of neither.
|
|
412
498
|
*
|
|
413
499
|
* **Two empties, and they mean opposite things (#218).** `null` — or an absent
|
|
414
500
|
* key, which reaches here as `undefined` — means _unset_: the note names no art
|
|
@@ -453,10 +539,10 @@ export function resolveImg(raw, config = loadPackConfig()) {
|
|
|
453
539
|
const s = String(raw);
|
|
454
540
|
// Blank on purpose — the caller's default must not apply.
|
|
455
541
|
if (s === "") return "";
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return s
|
|
542
|
+
// Somebody else's to serve — emit it exactly as authored.
|
|
543
|
+
if (addressesAnotherPackage(s)) return s;
|
|
544
|
+
// Ours, so root it where Foundry serves this package's files from.
|
|
545
|
+
return `${config.assetRoot}/${s}`;
|
|
460
546
|
}
|
|
461
547
|
|
|
462
548
|
/**
|
package/engine/index.mjs
CHANGED
|
@@ -69,6 +69,9 @@ export * as notePackage from "./note-package.mjs";
|
|
|
69
69
|
/** Frontmatter fields a note may no longer declare, and the refusal of them. */
|
|
70
70
|
export * as retiredFields from "./retired-fields.mjs";
|
|
71
71
|
|
|
72
|
+
/** Schema fields a note may never declare, because play writes them. */
|
|
73
|
+
export * as runtimeOnlyFields from "./runtime-only-fields.mjs";
|
|
74
|
+
|
|
72
75
|
/** The package homepage: the note type that compiles to a page, not a document. */
|
|
73
76
|
export * as homepage from "./homepage.mjs";
|
|
74
77
|
|
|
@@ -113,6 +116,12 @@ export * as siteBuild from "./site-build.mjs";
|
|
|
113
116
|
/** Address rules every content tree is linted against: shape, uniqueness, alias. */
|
|
114
117
|
export * as contentLint from "./content-lint.mjs";
|
|
115
118
|
|
|
119
|
+
/** The charset authored content is held to, so a book can choose its face. */
|
|
120
|
+
export * as contentCharset from "./content-charset.mjs";
|
|
121
|
+
|
|
122
|
+
/** Naming an interface icon in a note, without drawing one there. */
|
|
123
|
+
export * as contentIcons from "./content-icons.mjs";
|
|
124
|
+
|
|
116
125
|
/** Resolving every link in a tree, and the ones that land nowhere. */
|
|
117
126
|
export * as contentLinks from "./content-links.mjs";
|
|
118
127
|
|
package/engine/item-compiler.mjs
CHANGED
|
@@ -66,6 +66,9 @@ import { documentSubtype, subtypeRow } from "./document-subtypes.mjs";
|
|
|
66
66
|
// `system` verbatim, and `<system>.img` / `.effects` / `.flags` overriding
|
|
67
67
|
// their shared top-level forms for this system alone (#58).
|
|
68
68
|
import { blockField, blockProperty, claimedPaths, mergeSystemData } from "./system-block.mjs";
|
|
69
|
+
// The other direction of the same declaration: a field the *document* writes in
|
|
70
|
+
// play, which a note may not author and the builder does not emit (#330).
|
|
71
|
+
import { assertNoRuntimeOnlyFields } from "./runtime-only-fields.mjs";
|
|
69
72
|
|
|
70
73
|
/**
|
|
71
74
|
* The description an item carries: a pointer to its **item doc**, the
|
|
@@ -113,6 +116,17 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
113
116
|
*/
|
|
114
117
|
static requiresSystemBlock = true;
|
|
115
118
|
|
|
119
|
+
/**
|
|
120
|
+
* An Item carries an `img` — its icon — which this pass writes from the
|
|
121
|
+
* note's own path, falling back to the type's default art.
|
|
122
|
+
*
|
|
123
|
+
* `portrait` is **not** among them: a portrait is a being's sheet picture,
|
|
124
|
+
* and an item has nowhere to put one.
|
|
125
|
+
*
|
|
126
|
+
* @type {readonly string[]}
|
|
127
|
+
*/
|
|
128
|
+
static emitsArt = Object.freeze(["img"]);
|
|
129
|
+
|
|
116
130
|
/**
|
|
117
131
|
* The note-type → document-subtype map this pass compiles against.
|
|
118
132
|
*
|
|
@@ -174,6 +188,29 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
174
188
|
return !row || row.document === "Item";
|
|
175
189
|
}
|
|
176
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Refuse a note authoring one of its type's **runtime-only** fields (#330).
|
|
193
|
+
*
|
|
194
|
+
* A schema declares fields the document writes for itself — an affliction's
|
|
195
|
+
* `onsetDate` is the world time its onset fired at — and a note authoring
|
|
196
|
+
* one used to compile, because `<system>.system` is a verbatim passthrough
|
|
197
|
+
* and the field really is in the schema. The result was shipped content
|
|
198
|
+
* carrying one world's play state, with the build reporting success.
|
|
199
|
+
*
|
|
200
|
+
* The declaration says which, so nothing here knows a field name; see
|
|
201
|
+
* {@link module:engine/runtime-only-fields}.
|
|
202
|
+
*
|
|
203
|
+
* @param {object} fm - The note's frontmatter.
|
|
204
|
+
* @returns {void}
|
|
205
|
+
* @throws {Error} When the note authors one.
|
|
206
|
+
*/
|
|
207
|
+
assertAuthorable(fm) {
|
|
208
|
+
assertNoRuntimeOnlyFields(fm, itemFields(fm.type, this.system), {
|
|
209
|
+
block: this.system,
|
|
210
|
+
absPath: this.currentNote?.absPath,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
177
214
|
/**
|
|
178
215
|
* The Foundry Item subtype a note compiles into.
|
|
179
216
|
*
|
package/engine/journals.mjs
CHANGED
|
@@ -30,10 +30,13 @@
|
|
|
30
30
|
* any other: the macro pass reads the same page independently, and withholds
|
|
31
31
|
* nothing from the journal (#1514).
|
|
32
32
|
*
|
|
33
|
-
* Folder placement is identical to the items pack: `sohl.
|
|
34
|
-
* frontmatter is
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* Folder placement is identical to the items pack: `sohl.packFolder` in
|
|
34
|
+
* frontmatter is a folder **note's address**, resolved through the shared
|
|
35
|
+
* address index by the constructor's `folderResolver` (#255, #260). A folder
|
|
36
|
+
* materialises in every pack holding a document that names it, so a journals
|
|
37
|
+
* pack needs to declare nothing (#257) — which is what stopped this pass
|
|
38
|
+
* filing documentation into folders its own pack had never heard of. A
|
|
39
|
+
* documentation entry reuses its document's folder verbatim.
|
|
37
40
|
*
|
|
38
41
|
* Not a standalone script — exports the `Journals` compiler class, imported
|
|
39
42
|
* and driven by `packages/content-build/engine/generate.mjs` (via `npm run build:compiledb`).
|
|
@@ -311,6 +314,20 @@ export class Journals extends BasePackCompiler {
|
|
|
311
314
|
*/
|
|
312
315
|
static requiresId = false;
|
|
313
316
|
|
|
317
|
+
/**
|
|
318
|
+
* **None.** A JournalEntry has no artwork — no `img` property, and no
|
|
319
|
+
* nested place for one — so a note whose whole document is prose has
|
|
320
|
+
* nowhere to put an authored path (#349).
|
|
321
|
+
*
|
|
322
|
+
* The emptiness is the declaration, in the sense `JOURNAL_ONLY_FIELDS` is:
|
|
323
|
+
* it is what separates a pass that emits no art from one that has simply
|
|
324
|
+
* not said, and it is the fact the frontmatter lint reports a `lore` note's
|
|
325
|
+
* inert `img:` from.
|
|
326
|
+
*
|
|
327
|
+
* @type {readonly string[]}
|
|
328
|
+
*/
|
|
329
|
+
static emitsArt = Object.freeze([]);
|
|
330
|
+
|
|
314
331
|
/**
|
|
315
332
|
* How many of the compiled entries were documentation for a document
|
|
316
333
|
* compiled elsewhere, for the summary.
|
package/engine/macros.mjs
CHANGED
|
@@ -295,6 +295,14 @@ export class Macros extends BasePackCompiler {
|
|
|
295
295
|
*/
|
|
296
296
|
static convertsWikilinks = false;
|
|
297
297
|
|
|
298
|
+
/**
|
|
299
|
+
* A Macro carries an `img` — the tile art Foundry shows on the hotbar —
|
|
300
|
+
* defaulting to {@link DEFAULT_MACRO_IMG} where the note names none.
|
|
301
|
+
*
|
|
302
|
+
* @type {readonly string[]}
|
|
303
|
+
*/
|
|
304
|
+
static emitsArt = Object.freeze(["img"]);
|
|
305
|
+
|
|
298
306
|
/**
|
|
299
307
|
* @param {object} fm - The note's frontmatter.
|
|
300
308
|
* @returns {boolean} True for a `macro` note.
|
package/engine/map-notes.mjs
CHANGED
|
@@ -56,9 +56,9 @@ import { compendiumUuid, makeId, MAP_SUBTYPES, MAP_TYPES } from "./ids.mjs";
|
|
|
56
56
|
// bridge (`SohlRegionTriggerBehavior`), so an event this build accepts is
|
|
57
57
|
// exactly one the bridge forwards.
|
|
58
58
|
import { CURATED_REGION_EVENTS, EXCLUDED_REGION_EVENTS } from "./region-events.mjs";
|
|
59
|
-
// A map's background art is `img`, as every other note type's art is
|
|
60
|
-
//
|
|
61
|
-
import {
|
|
59
|
+
// A map's background art is `img`, as every other note type's art is. `image`,
|
|
60
|
+
// the spelling a map alone once used, is retired and gone (#149).
|
|
61
|
+
import { sohlField } from "./frontmatter.mjs";
|
|
62
62
|
|
|
63
63
|
/* -------------------------------------------------------------------- */
|
|
64
64
|
/* Note types and their canvas profiles */
|
|
@@ -907,7 +907,7 @@ export function buildScene(fm, ctx) {
|
|
|
907
907
|
// Read from the note rather than from its `sohl:` block: art is not
|
|
908
908
|
// system-specific, so `img` is authored at the top level like every other
|
|
909
909
|
// type's, and `sohlField` honours the block for anything already there.
|
|
910
|
-
const img =
|
|
910
|
+
const img = sohlField(fm, "img");
|
|
911
911
|
if (!img) throw new Error("a map note needs an `img`");
|
|
912
912
|
|
|
913
913
|
const warn = (message) => {
|
|
@@ -981,11 +981,11 @@ export function buildScene(fm, ctx) {
|
|
|
981
981
|
* @param {string} sceneId - The owning scene's `_id`.
|
|
982
982
|
* @param {string} [img] - The background art, already resolved from the note.
|
|
983
983
|
* Passed by {@link buildScene}, which reads it from the note rather than from
|
|
984
|
-
* the block; defaults to
|
|
985
|
-
*
|
|
984
|
+
* the block; defaults to the block's own `img`, so a direct two-argument call
|
|
985
|
+
* still works.
|
|
986
986
|
* @returns {object} The Level document, keyed for the pack.
|
|
987
987
|
*/
|
|
988
|
-
export function buildLevel(sohl, sceneId, img =
|
|
988
|
+
export function buildLevel(sohl, sceneId, img = sohlField({ sohl }, "img")) {
|
|
989
989
|
const level = {
|
|
990
990
|
_id: DEFAULT_LEVEL_ID,
|
|
991
991
|
name: sohl.levelName ?? "Ground",
|