@heroiclands/package-build 21.1.0 → 21.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/CONTENT.md +107 -3
  3. package/assets/fonts/libertinus/LibertinusMono-Regular.otf +0 -0
  4. package/assets/fonts/libertinus/LibertinusSans-Bold.otf +0 -0
  5. package/assets/fonts/libertinus/LibertinusSans-Italic.otf +0 -0
  6. package/assets/fonts/libertinus/LibertinusSans-Regular.otf +0 -0
  7. package/assets/fonts/libertinus/OFL.txt +94 -0
  8. package/assets/fonts/libertinus/provenance.yaml +11 -0
  9. package/assets/images/banners/afflictionbnr.webp +0 -0
  10. package/assets/images/banners/armorclothingbnr.webp +0 -0
  11. package/assets/images/banners/attributebnr.webp +0 -0
  12. package/assets/images/banners/containerbnr.webp +0 -0
  13. package/assets/images/banners/devdocsbnr.webp +0 -0
  14. package/assets/images/banners/miscgearbnr.webp +0 -0
  15. package/assets/images/banners/mysticalabilitybnr.webp +0 -0
  16. package/assets/images/banners/projectilebnr.webp +0 -0
  17. package/assets/images/banners/rulesbnr.webp +0 -0
  18. package/assets/images/banners/skillbnr.webp +0 -0
  19. package/assets/images/banners/traumabnr.webp +0 -0
  20. package/assets/images/banners/userguidebnr.webp +0 -0
  21. package/assets/images/banners/weapongearbnr.webp +0 -0
  22. package/assets/images/provenance.yaml +4 -0
  23. package/bin/content-build.mjs +19 -5
  24. package/ci/emit-asset-index.mjs +32 -0
  25. package/content-config.mjs +30 -1
  26. package/docs/api.md +100 -26
  27. package/docs/configuration.md +16 -7
  28. package/docs/content-format.md +512 -109
  29. package/engine/actor-compiler.mjs +58 -9
  30. package/engine/art-fields.mjs +292 -0
  31. package/engine/asset-index.mjs +397 -0
  32. package/engine/asset-types.mjs +192 -0
  33. package/engine/base-compiler.mjs +69 -1
  34. package/engine/bundles.mjs +5 -5
  35. package/engine/compile-corpus.mjs +4 -0
  36. package/engine/content-address.mjs +14 -2
  37. package/engine/content-charset.mjs +5 -1
  38. package/engine/content-embeds.mjs +314 -0
  39. package/engine/content-html.mjs +5 -1
  40. package/engine/content-icons.mjs +5 -1
  41. package/engine/content-images.mjs +26 -5
  42. package/engine/content-index.mjs +68 -33
  43. package/engine/content-links.mjs +164 -24
  44. package/engine/frontmatter-lint.mjs +36 -23
  45. package/engine/helpers.mjs +83 -31
  46. package/engine/index-records.mjs +63 -4
  47. package/engine/index.mjs +15 -0
  48. package/engine/infobox-registry.mjs +50 -3
  49. package/engine/infobox.mjs +12 -8
  50. package/engine/item-compiler.mjs +10 -10
  51. package/engine/journals.mjs +3 -0
  52. package/engine/macros.mjs +16 -13
  53. package/engine/map-notes.mjs +29 -22
  54. package/engine/metadata-index.mjs +44 -36
  55. package/engine/note-vocabulary.mjs +71 -29
  56. package/engine/pack-router.mjs +14 -0
  57. package/engine/packagebuild-index.mjs +124 -0
  58. package/engine/packages.mjs +83 -0
  59. package/engine/pathnames.mjs +74 -0
  60. package/engine/pdf-build.mjs +133 -8
  61. package/engine/pdf-render.mjs +9 -2
  62. package/engine/scenes.mjs +12 -9
  63. package/engine/site-build.mjs +43 -16
  64. package/engine/site-index.mjs +10 -1
  65. package/engine/subtype-registry.mjs +21 -0
  66. package/engine/web-wikilinks.mjs +46 -12
  67. package/engine/wikilink-syntax.mjs +20 -2
  68. package/engine/wikilinks.mjs +15 -2
  69. package/hm3/actors.mjs +11 -38
  70. package/package.json +5 -2
  71. package/packagebuild-metadata.jsonl +13 -0
  72. package/sohl/actors.mjs +12 -86
  73. package/sohl/note-schemas.mjs +4 -16
  74. package/types/content-config.d.mts +8 -0
  75. package/types/engine/actor-compiler.d.mts +29 -0
  76. package/types/engine/art-fields.d.mts +192 -0
  77. package/types/engine/asset-index.d.mts +92 -0
  78. package/types/engine/asset-types.d.mts +110 -0
  79. package/types/engine/base-compiler.d.mts +42 -1
  80. package/types/engine/compile-corpus.d.mts +4 -1
  81. package/types/engine/content-charset.d.mts +4 -0
  82. package/types/engine/content-embeds.d.mts +218 -0
  83. package/types/engine/content-html.d.mts +4 -0
  84. package/types/engine/content-icons.d.mts +4 -0
  85. package/types/engine/content-images.d.mts +15 -2
  86. package/types/engine/content-index.d.mts +27 -15
  87. package/types/engine/frontmatter-lint.d.mts +11 -14
  88. package/types/engine/helpers.d.mts +7 -2
  89. package/types/engine/index-records.d.mts +44 -3
  90. package/types/engine/index.d.mts +5 -0
  91. package/types/engine/infobox-registry.d.mts +36 -1
  92. package/types/engine/infobox.d.mts +6 -4
  93. package/types/engine/macros.d.mts +9 -5
  94. package/types/engine/map-notes.d.mts +10 -6
  95. package/types/engine/metadata-index.d.mts +2 -17
  96. package/types/engine/note-vocabulary.d.mts +30 -0
  97. package/types/engine/packagebuild-index.d.mts +56 -0
  98. package/types/engine/packages.d.mts +62 -0
  99. package/types/engine/pathnames.d.mts +41 -81
  100. package/types/engine/pdf-build.d.mts +58 -1
  101. package/types/engine/pdf-render.d.mts +3 -1
  102. package/types/engine/scenes.d.mts +2 -1
  103. package/types/engine/site-build.d.mts +5 -1
  104. package/types/engine/site-index.d.mts +4 -1
  105. package/types/engine/subtype-registry.d.mts +20 -0
  106. package/types/engine/web-wikilinks.d.mts +14 -4
  107. package/types/engine/wikilink-syntax.d.mts +13 -1
  108. package/types/engine/wikilinks.d.mts +7 -1
  109. package/types/sohl/actors.d.mts +0 -1
@@ -83,13 +83,17 @@ export function resolveMacroScope(fm: object, label: string): string;
83
83
  * @param {string} opts.command - The command, from {@link macroCommand}.
84
84
  * @param {string|null} [opts.folder] - The resolved folder id.
85
85
  * @param {object} [opts.stats] - The `_stats` block.
86
+ * @param {string|null} [opts.img] - The resolved art, from the note's `icon`
87
+ * address. Passed in rather than resolved here, because an address is
88
+ * answered by the compile's index and this function takes no index.
86
89
  * @returns {MacroDocument} The Macro document.
87
90
  * @throws {Error} When the frontmatter's macro type or scope is unusable.
88
91
  */
89
- export function buildMacroEntry(fm: object, { command, folder, stats }: {
92
+ export function buildMacroEntry(fm: object, { command, folder, stats, img }: {
90
93
  command: string;
91
94
  folder?: string | null | undefined;
92
95
  stats?: object | undefined;
96
+ img?: string | null | undefined;
93
97
  }): MacroDocument;
94
98
  /**
95
99
  * The anchor the executable script lives under: `# Script {#script}`.
@@ -112,11 +116,11 @@ export const MACRO_TYPES: readonly string[];
112
116
  /** The Foundry macro scopes (`CONST.MACRO_SCOPES`), in schema order. */
113
117
  export const MACRO_SCOPES: readonly string[];
114
118
  /**
115
- * Foundry's own default macro artwork, used when a note authors no `img`.
119
+ * Foundry's own default macro artwork, used when a note names no `icon`.
116
120
  *
117
- * A core path, deliberately: it is not translated by {@link resolveImg} (which
118
- * roots `icons/…` under this system's assets), so it must be stated after that
119
- * translation rather than as authored frontmatter.
121
+ * A core path, deliberately: it names a file Foundry itself ships rather than an
122
+ * address in any package, so it is stated after the art resolution rather than
123
+ * as something a note could author.
120
124
  */
121
125
  export const DEFAULT_MACRO_IMG: "icons/svg/dice-target.svg";
122
126
  /**
@@ -140,7 +140,8 @@ export function buildShape(spec: object, geom: MapGeometry): object;
140
140
  * `packageId`, `journalEntryId`, `journalPack` (the pack the note's derived
141
141
  * JournalEntry landed in), `pageIds` (heading key → page id),
142
142
  * `resolveRegionRef` / `resolveBehaviorRef` / `resolveEffectRef` (address →
143
- * UUID), `knownActions`, `warnings`, and optionally `folder` and `stats`.
143
+ * UUID), `art` (an art address the path each surface serves),
144
+ * `knownActions`, `warnings`, and optionally `folder` and `stats`.
144
145
  * @returns {object} The Scene document, keyed for the pack.
145
146
  * @throws {Error} On any authoring mistake Foundry would accept silently.
146
147
  */
@@ -156,13 +157,16 @@ export function buildScene(fm: object, ctx: object): object;
156
157
  *
157
158
  * @param {object} sohl - The note's `sohl:` block.
158
159
  * @param {string} sceneId - The owning scene's `_id`.
159
- * @param {string} [img] - The background art, already resolved from the note.
160
- * Passed by {@link buildScene}, which reads it from the note rather than from
161
- * the block; defaults to the block's own `img`, so a direct two-argument call
162
- * still works.
160
+ * @param {string} img - The background art, already resolved from the note's
161
+ * `bgImage` address by {@link buildScene}. Passed rather than resolved here,
162
+ * because an address is answered by the compile's index and this function
163
+ * takes none.
164
+ * @param {(value: unknown, key: string, type: string) => string|null} [art] -
165
+ * The art resolver, for the foreground overlay. Omitted, a note naming one
166
+ * gets no overlay rather than a path nothing serves.
163
167
  * @returns {object} The Level document, keyed for the pack.
164
168
  */
165
- export function buildLevel(sohl: object, sceneId: string, img?: string): object;
169
+ export function buildLevel(sohl: object, sceneId: string, img: string, art?: (value: unknown, key: string, type: string) => string | null): object;
166
170
  /**
167
171
  * Compile the `walls:` and `doors:` blocks into Wall documents.
168
172
  *
@@ -1,20 +1,3 @@
1
- /**
2
- * What a package's content index is called, wherever it is written or fetched.
3
- *
4
- * **The local index and the published artifact are one file.** A package emits
5
- * this, ships it as a release asset, and advertises it as `flags.metadataUrl`;
6
- * a consumer fetches that same file into its cache and reads it. Naming it in
7
- * one function is what keeps the emitter, the release and the fetcher from
8
- * drifting into three spellings of one artifact.
9
- *
10
- * The `-metadata` suffix earns its place: a bare `<package>.jsonl` says nothing
11
- * about what it holds, and these files land in a cache directory beside other
12
- * packages' artifacts where the name is all a reader has.
13
- *
14
- * @param {string} pkg - The content package name.
15
- * @returns {string} The file name, e.g. `sohl-metadata.jsonl`.
16
- */
17
- export function metadataFileName(pkg: string): string;
18
1
  /**
19
2
  * Every dependency whose published index this build resolves addresses through.
20
3
  *
@@ -211,6 +194,7 @@ export function formatUnaddressableFinding(finding: {
211
194
  entries: number;
212
195
  sampleKey: string;
213
196
  }, config: object): string;
197
+ export { metadataFileName };
214
198
  /**
215
199
  * The relationship kinds that are dependencies, and therefore citable.
216
200
  *
@@ -224,3 +208,4 @@ export function formatUnaddressableFinding(finding: {
224
208
  */
225
209
  export const METADATA_RELATIONSHIP_KINDS: readonly string[];
226
210
  export function isComplete(dir: string): boolean;
211
+ import { metadataFileName } from "./packages.mjs";
@@ -115,6 +115,9 @@ export function assertVocabularyCharset(vocabulary: Readonly<Record<string, Type
115
115
  * @param {string} type - The note's `type`.
116
116
  * @param {Readonly<Record<string, TypeVocabulary>>} [vocabulary] - The registry
117
117
  * to read, defaulting to {@link NOTE_VOCABULARY}.
118
+ * {@link SHARED_DATA_FIELDS} come first, because they are part of every type's
119
+ * declaration and a caller asking what a type accepts must be told all of it.
120
+ *
118
121
  * @returns {readonly DataFieldSpec[]|undefined} The declaration, or `undefined`
119
122
  * when the type declares none — which is not the same as declaring an empty
120
123
  * one, and is why the lint makes no claim rather than refusing every key.
@@ -131,6 +134,26 @@ export function dataFields(type: string, vocabulary?: Readonly<Record<string, Ty
131
134
  * it has no `subType` at all — see {@link TypeVocabulary}.
132
135
  */
133
136
  export function subTypes(type: string, vocabulary?: Readonly<Record<string, TypeVocabulary>>): readonly string[] | null | undefined;
137
+ /**
138
+ * The `data:` keys **every** note type accepts, whatever it is.
139
+ *
140
+ * `data:` is a closed container and the per-type vocabularies are the only
141
+ * lists there are, so a key legal on every type needs somewhere that is not one
142
+ * type's list — including the types whose own vocabulary is empty. Repeating a
143
+ * row in twenty-five tables would be twenty-five chances for one of them to
144
+ * disagree with the rest.
145
+ *
146
+ * Both are art slots, and they are legal everywhere for different reasons.
147
+ * `icon` is the document's profile art and most types compile into a document
148
+ * that carries one; where a type's passes emit none, the frontmatter lint says
149
+ * so as a warning rather than the vocabulary refusing the key, because the
150
+ * value may still be read by a page template. `banner` reaches no compiled
151
+ * document at all — it is the page's hero image — and a page is what every note
152
+ * publishes.
153
+ *
154
+ * @type {readonly DataFieldSpec[]}
155
+ */
156
+ export const SHARED_DATA_FIELDS: readonly DataFieldSpec[];
134
157
  /**
135
158
  * The declared tag that marks a note as **unfinished**.
136
159
  *
@@ -269,6 +292,13 @@ export type DataFieldSpec = {
269
292
  * one the message holds.
270
293
  */
271
294
  entryShape?: string | undefined;
295
+ /**
296
+ * - The type a bare value takes. An art slot declares
297
+ * one — `icon` for `icon` and `tokenIcon`, `image` for `bgImage` and
298
+ * `banner` — so `icon: anvil` names `icon-anvil` while a value carrying the
299
+ * separator states its own address.
300
+ */
301
+ ref?: string | undefined;
272
302
  /**
273
303
  * - For a `scalar-or-map` field, what its keys name.
274
304
  * `"pack"` means each is a pack this package declares, so a key naming none
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The index records for the files this package ships.
3
+ *
4
+ * Walked rather than read back, so an installed copy and a git checkout answer
5
+ * alike and there is no state to be stale. The tree is small — a handful of
6
+ * banners — so the walk costs nothing a build would notice.
7
+ *
8
+ * @param {string} [assetsBase] - The asset directory, for a caller testing
9
+ * against a tree of its own.
10
+ * @returns {Array<Record<string, any>>} One record per addressable file.
11
+ */
12
+ export function packageBuildRecords(assetsBase?: string): Array<Record<string, any>>;
13
+ /**
14
+ * Publish the index as a file, for the readers that are not this process.
15
+ *
16
+ * The website, the book and the Foundry runtime each read a package's index as
17
+ * JSON Lines; this package's has to be one too, and it ships in the tarball
18
+ * beside the images it describes.
19
+ *
20
+ * @param {object} [options] - Options.
21
+ * @param {string} [options.assetsBase] - The asset directory to walk.
22
+ * @param {string} [options.file] - Where to write it.
23
+ * @returns {{file: string, assets: number, bytes: number}} Where it was
24
+ * written, how many files it holds, and its size.
25
+ */
26
+ export function emitPackageBuildIndex({ assetsBase, file }?: {
27
+ assetsBase?: string | undefined;
28
+ file?: string | undefined;
29
+ }): {
30
+ file: string;
31
+ assets: number;
32
+ bytes: number;
33
+ };
34
+ /**
35
+ * This package's own root, wherever it is installed.
36
+ *
37
+ * Derived from this module's location rather than from a configuration: the
38
+ * files being indexed sit beside it in the same tarball, so the one reliable
39
+ * answer is "up from here". A consuming repository's `rootDir` names its own
40
+ * tree and says nothing about where its dependencies live.
41
+ *
42
+ * @type {string}
43
+ */
44
+ export const PACKAGEBUILD_ROOT: string;
45
+ /**
46
+ * The asset directory this package ships.
47
+ *
48
+ * @type {string}
49
+ */
50
+ export const PACKAGEBUILD_ASSETS: string;
51
+ /**
52
+ * Where the published index sits, beside the files it describes.
53
+ *
54
+ * @type {string}
55
+ */
56
+ export const PACKAGEBUILD_INDEX_FILE: string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * What a package's content index is called, wherever it is written or fetched.
3
+ *
4
+ * **The local index and the published artifact are one file.** A package emits
5
+ * this, ships it as a release asset, and advertises it as `flags.metadataUrl`;
6
+ * a consumer fetches that same file into its cache and reads it. Naming it in
7
+ * one function is what keeps the emitter, the release and the fetcher from
8
+ * drifting into three spellings of one artifact.
9
+ *
10
+ * The `-metadata` suffix earns its place: a bare `<package>.jsonl` says nothing
11
+ * about what it holds, and these files land in a cache directory beside other
12
+ * packages' artifacts where the name is all a reader has.
13
+ *
14
+ * It lives here rather than beside the loader because the loader reaches the
15
+ * toolchain's own index, which has to name its file without importing the
16
+ * loader back.
17
+ *
18
+ * @param {string} pkg - The content package name.
19
+ * @returns {string} The file name, e.g. `sohl-metadata.jsonl`.
20
+ */
21
+ export function metadataFileName(pkg: string): string;
22
+ /**
23
+ * Whether a package name is held back from the open registry.
24
+ *
25
+ * @param {unknown} pkg - The candidate `contentPackage`.
26
+ * @returns {boolean} True when the name is reserved.
27
+ */
28
+ export function isReservedPackage(pkg: unknown): boolean;
29
+ /**
30
+ * The `<package>` segment's own registry: the names no repository may claim.
31
+ *
32
+ * A content package names itself, so the registry is open — and exactly one
33
+ * name is held back from it.
34
+ *
35
+ * **`packagebuild` is a package that is not a package.** package-build is an npm
36
+ * package rather than a system or a module, and it ships a set of images —
37
+ * section banners chiefly — that many packages draw on. Addressing them as
38
+ * `packagebuild-none-image-<shortcode>` lets a note reach one without declaring a
39
+ * dependency on some parent system or module it otherwise has no relationship
40
+ * with, which is the whole point: the alternative is every package depending on
41
+ * one of the others just to borrow a banner.
42
+ *
43
+ * So nothing may create a real package that collides with the name, and its
44
+ * resolution is special-cased, because no installed directory sits behind it.
45
+ *
46
+ * This module is a **leaf with no local imports**, so the configuration
47
+ * validator can name it without closing a cycle around `content-config.mjs`.
48
+ *
49
+ * @module
50
+ */
51
+ /**
52
+ * The address namespace package-build's own assets publish under.
53
+ *
54
+ * @type {string}
55
+ */
56
+ export const PACKAGEBUILD_PACKAGE: string;
57
+ /**
58
+ * Every package name a repository may not claim.
59
+ *
60
+ * @type {ReadonlySet<string>}
61
+ */
62
+ export const RESERVED_PACKAGES: ReadonlySet<string>;
@@ -9,6 +9,47 @@
9
9
  * @returns {string} The problem, as a finding's sentence, or `""`.
10
10
  */
11
11
  export function pathnameProblem(raw: string | null | undefined): string;
12
+ /**
13
+ * Whether this build installs anything into a Foundry data directory.
14
+ *
15
+ * A `documentation` package compiles no packs and installs nowhere, so no note
16
+ * of its reaches a Foundry document and no pathname of its can be dead on a
17
+ * surface it never touches. A check that reports a missing Foundry address asks
18
+ * this first, so the one kind with no Foundry surface is not told about it.
19
+ *
20
+ * Read from {@link packageAddresses} rather than from a key, so "does this
21
+ * package have a Foundry root?" is answered in one place by the map that
22
+ * derives every other package's.
23
+ *
24
+ * @param {object} config - The resolved build configuration.
25
+ * @returns {boolean} Whether the package being built has a Foundry root.
26
+ */
27
+ export function servesFoundry(config: object): boolean;
28
+ /**
29
+ * Why a pathname has no Foundry address, or `""` when it has one.
30
+ *
31
+ * The sibling of {@link pathnameProblem} one step further on: that one asks
32
+ * whether a pathname is written correctly at all, and this one asks whether
33
+ * *this* build can turn a correctly written pathname into a path a Foundry
34
+ * install serves. Both answer in a sentence rather than a code, so the caller
35
+ * that holds a line and a column and the caller that holds only a file say the
36
+ * same thing about the same value.
37
+ *
38
+ * `""` for every pathname that resolves — and for the three shapes that
39
+ * legitimately reach Foundry untranslated: a URL, a protocol-relative
40
+ * `//host/…`, and a `/`-rooted path Foundry serves from its data root. A blank
41
+ * pathname is a deliberate blank and resolves to a blank on every surface, so
42
+ * it is not a problem either.
43
+ *
44
+ * `""` as well for a pathname {@link pathnameProblem} already refuses. That
45
+ * defect has its own finding naming its own replacement, and reporting a second
46
+ * one about the same characters would send the author to one mistake twice.
47
+ *
48
+ * @param {string|null|undefined} raw - The pathname, as authored.
49
+ * @param {object} config - The resolved build configuration.
50
+ * @returns {string} The problem, as a finding's sentence, or `""`.
51
+ */
52
+ export function foundryAddressProblem(raw: string | null | undefined, config: object): string;
12
53
  /**
13
54
  * Every content package this build can resolve a pathname against.
14
55
  *
@@ -41,87 +82,6 @@ export function packageAddresses(config: object): Map<string, {
41
82
  * resolves on one surface and nowhere else.
42
83
  */
43
84
  export function resolvePathname(raw: string | null | undefined, config: object): PathnameForms | null;
44
- /**
45
- * One authored pathname, and the four addresses it resolves to.
46
- *
47
- * A note names a file once — in `img:`, in `data.portrait:`, in the body of a
48
- * markdown image — and four surfaces have to serve it: a Foundry install, this
49
- * repository's own working tree, the website, and the book. Each addresses the
50
- * same file differently, so the authored pathname is a *statement of
51
- * ownership* and every surface derives its own address from it. One statement,
52
- * four derivations, one rule.
53
- *
54
- * ## The rule
55
- *
56
- * **The first segment says which package owns the file, when it is followed by
57
- * `assets/`.** Everything after `assets/` is the *suffix* — the path inside
58
- * that package's shipped tree, and the one piece every form is built from.
59
- *
60
- * | Authored | Owner | Suffix |
61
- * | --------------------------- | ---------------- | ------------------- |
62
- * | `sohl/assets/icons/a.svg` | the `sohl` package | `icons/a.svg` |
63
- * | `images/beings/b.webp` | **this** package | `images/beings/b.webp` |
64
- *
65
- * A pathname that does not open with `<package>/assets/` belongs to the package
66
- * being built, and the whole of it is the suffix. That is the ordinary case and
67
- * the one nearly every note writes.
68
- *
69
- * The four forms, for a `thalorna` note writing `images/map.webp` (`thalorna`
70
- * ships as the Foundry module `sohl-thalorna`):
71
- *
72
- * | Form | Address |
73
- * | --------- | -------------------------------------------------- |
74
- * | `foundry` | `modules/sohl-thalorna/assets/images/map.webp` |
75
- * | `local` | `assets/images/map.webp` |
76
- * | `web` | `https://cdn.heroiclands.org/thalorna/images/map.webp` |
77
- * | `pdf` | `assets/images/map.webp` |
78
- *
79
- * **`<package>` and `<foundry-id>` are two different names.** The package is
80
- * `thalorna` — what the content is called, what the website serves it under,
81
- * and what a note writes. The Foundry id is `sohl-thalorna` — what Foundry
82
- * installs the module as, and the only place that name appears. They coincide
83
- * for `sohl` and `hm3`, which is exactly why the two are kept apart here rather
84
- * than treated as one value.
85
- *
86
- * `local` and `pdf` read the same and mean different places: `local` is the file
87
- * in the owning repository's working tree, `pdf` is where the book stages a copy
88
- * beside its Typst source. They are derived separately because only one of them
89
- * is a file a build may open — see {@link PathnameForms.own}.
90
- *
91
- * ## What is not a package pathname
92
- *
93
- * **An off-install address passes through on every surface**: a URL, a
94
- * protocol-relative `//host/…`, or a `/`-rooted path, which Foundry serves from
95
- * the data root and which names no package at all. That is how a note addresses
96
- * core Foundry art (`/icons/svg/mystery-man.svg`) or a package this build knows
97
- * nothing about (`/systems/dnd5e/icons/spell.webp`).
98
- *
99
- * **A package this build has never heard of keeps its ownership.** The website
100
- * and the book need only the package's name and the suffix, so both resolve;
101
- * the Foundry address needs the package's kind and its Foundry id, which only a
102
- * declared relationship carries, so that one form comes back `null` and the
103
- * caller that needs it refuses. Reading such a pathname as this package's own
104
- * would file one package's name inside another's tree and say nothing.
105
- *
106
- * **A `systems/…` or `modules/…` pathname is refused.** It is a Foundry address
107
- * written where an ownership statement belongs: it resolves for Foundry and for
108
- * nothing else, because neither the website nor the book has any such directory.
109
- * {@link pathnameProblem} names the replacement, and every surface refuses the
110
- * value rather than deriving an address from it — a wrong address that resolves
111
- * to a 404 is the failure this module exists to remove, and inventing one here
112
- * would reintroduce it one directory along.
113
- *
114
- * ## The two empties
115
- *
116
- * `null` — or an absent key, which arrives as `undefined` — means **unset**: the
117
- * note names no file and the caller's default applies. `""` means **blank on
118
- * purpose**: the note names no file and wants none, so no default may replace
119
- * it. `resolvePathname` returns `null` for the first and a form object whose
120
- * every address is `""` for the second, so the two stay distinguishable all the
121
- * way to the caller.
122
- *
123
- * @module
124
- */
125
85
  /**
126
86
  * The directory a package ships its files in, and the segment that marks a
127
87
  * pathname's first segment as a package name.
@@ -74,15 +74,72 @@ export function buildPdf({ config, out, version, compile }?: {
74
74
  version?: string | undefined;
75
75
  compile?: boolean | undefined;
76
76
  }): Promise<object>;
77
+ /**
78
+ * The command line the compile runs, as data.
79
+ *
80
+ * Separate from running it so the flags that decide which faces are in play
81
+ * can be asserted without a compiler installed — which is the half of the
82
+ * invocation that changes what the book looks like.
83
+ *
84
+ * @param {string} typPath - The `.typ` file.
85
+ * @param {string} pdfPath - Where the PDF goes.
86
+ * @param {object} [pdf] - The resolved `pdf:` block.
87
+ * @returns {string[]} The arguments, in order.
88
+ */
89
+ export function typstArgs(typPath: string, pdfPath: string, pdf?: object): string[];
90
+ /**
91
+ * The compiler's own warnings, as findings.
92
+ *
93
+ * A compile that says `unknown font family` still exits 0 and still writes a
94
+ * book — one set in whatever face the fallback reached. That is the failure
95
+ * this surface is least able to see, so the compiler's warnings are read back
96
+ * and reported on the same terms as everything else the build finds.
97
+ *
98
+ * Typst writes a warning as a `warning:` line followed by a `┌─ file:line:col`
99
+ * locator over a source excerpt. The message and the position are taken; the
100
+ * excerpt is not, since the reader has the file.
101
+ *
102
+ * @param {string} output - What the compiler wrote to stderr.
103
+ * @returns {Array<{file: string, line?: number, column?: number,
104
+ * severity: string, message: string}>} One finding per warning.
105
+ */
106
+ export function typstWarnings(output: string): Array<{
107
+ file: string;
108
+ line?: number;
109
+ column?: number;
110
+ severity: string;
111
+ message: string;
112
+ }>;
77
113
  /**
78
114
  * Run Typst over the emitted source.
79
115
  *
80
116
  * @param {string} typPath - The `.typ` file.
81
117
  * @param {string} pdfPath - Where the PDF goes.
82
118
  * @param {object} pdf - The resolved `pdf:` block.
83
- * @returns {{ok: boolean, message: string}} What happened.
119
+ * @returns {{ok: boolean, message: string, findings: object[]}} What happened,
120
+ * and what the compiler warned about on the way.
84
121
  */
85
122
  export function compileTypst(typPath: string, pdfPath: string, pdf?: object): {
86
123
  ok: boolean;
87
124
  message: string;
125
+ findings: object[];
88
126
  };
127
+ /**
128
+ * The faces the book is set in that the compiler does not carry itself.
129
+ *
130
+ * Resolved from this module rather than from the working directory, on the same
131
+ * rule the specification and `--version` follow: a consumer runs the build
132
+ * inside its own repository, and the faces it sets the book in are the ones
133
+ * that came with the toolchain version it resolved.
134
+ *
135
+ * What is here is the **sans**, in the three styles a heading can ask for, the
136
+ * superfamily's **mono** for a package that names it, and the licence they
137
+ * travel under. The serif is not: the compiler embeds one, and a second copy of
138
+ * a face it already carries is a file nothing selects.
139
+ *
140
+ * It is **not** an addressable asset root: the compiler matches a face by
141
+ * family name, so nothing addresses these files and nothing needs to.
142
+ *
143
+ * @type {string}
144
+ */
145
+ export const BOOK_FONTS_PATH: string;
@@ -198,7 +198,9 @@ export function bookTypstPreamble(): string;
198
198
  * @param {string} opts.title - The document's title.
199
199
  * @param {string} [opts.subtitle] - Shown under it on the title page.
200
200
  * @param {string[]} [opts.front] - Rendered Typst for each front-matter file.
201
- * @param {object} [opts.fonts] - `{ serif, sans, mono }` family names.
201
+ * @param {object} [opts.fonts] - `{ serif, sans, mono }` family names. Each
202
+ * falls back to the face the toolchain ships or the compiler embeds, so a
203
+ * caller that names none still sets the book in all three.
202
204
  * @param {string} [opts.version] - Stamped on the title page when given.
203
205
  * @param {string} [opts.preamble] - Definitions the bodies call, emitted once
204
206
  * above the title page. A panel every entry draws is a set of rules stated
@@ -23,8 +23,9 @@ export function collectKnownActionNames(repoRoot: string): Set<string>;
23
23
  * scene's id-based references resolve on import.
24
24
  */
25
25
  export class Scenes extends BasePackCompiler {
26
- constructor({ contentBase, dest, skipDirectories, companionDests, folderResolver, repoRoot, }: {
26
+ constructor({ contentBase, assetsBase, dest, skipDirectories, companionDests, folderResolver, repoRoot, }: {
27
27
  contentBase: any;
28
+ assetsBase: any;
28
29
  dest: any;
29
30
  skipDirectories: any;
30
31
  companionDests?: {} | undefined;
@@ -237,15 +237,19 @@ export function sectionFrontmatter(meta: object): object;
237
237
  * for.
238
238
  * @param {(data: object, page: object) => void} [options.decorate] - Called
239
239
  * with each page's frontmatter, for whatever a consumer's own pass adds.
240
+ * @param {(value: unknown, type: string) => string|null} [options.artSrc] -
241
+ * Translates an art address into the pathname the website resolver takes, or
242
+ * `null` where nothing answers it.
240
243
  * @param {(src: string) => string} [options.webSrc] - Translates an authored
241
244
  * pathname into the address the website serves. Every artwork field goes
242
245
  * through it, so a page's `img:` and its body images name the same file the
243
246
  * same way.
244
247
  * @returns {object} The frontmatter to write.
245
248
  */
246
- export function pageFrontmatter(page: object, { readmeSections, decorate, webSrc }: {
249
+ export function pageFrontmatter(page: object, { readmeSections, decorate, webSrc, artSrc }: {
247
250
  readmeSections?: Record<string, object> | undefined;
248
251
  decorate?: ((data: object, page: object) => void) | undefined;
252
+ artSrc?: ((value: unknown, type: string) => string | null) | undefined;
249
253
  webSrc?: ((src: string) => string) | undefined;
250
254
  }): object;
251
255
  /**
@@ -34,6 +34,8 @@ export function buildSiteIndex(entries: readonly SiteEntry[], { foreignIndex }?:
34
34
  * diagnostic names. Absent, `src` stands in.
35
35
  * @param {Map<string, object>} [options.foreignIndex] - The foreign index, for
36
36
  * resolvers that distinguish a foreign hit from a local one.
37
+ * @param {object} [options.assets] - The address space an `![[…]]` embed
38
+ * resolves against, shaped as every asset resolver reads one.
37
39
  * @returns {object} The resolver context.
38
40
  *
39
41
  * There is deliberately **no `manifestsComplete`**. It used to let a resolver
@@ -42,12 +44,13 @@ export function buildSiteIndex(entries: readonly SiteEntry[], { foreignIndex }?:
42
44
  * checker never had it and one authored link must not get two verdicts. A
43
45
  * caller still passing it is ignored rather than obeyed.
44
46
  */
45
- export function wikiContext(built: SiteIndex, { src, file, type, errors, foreignIndex }: {
47
+ export function wikiContext(built: SiteIndex, { src, file, type, errors, foreignIndex, assets }: {
46
48
  src: string;
47
49
  type?: string | null | undefined;
48
50
  errors: object[];
49
51
  file?: string | undefined;
50
52
  foreignIndex?: Map<string, object> | undefined;
53
+ assets?: object | undefined;
51
54
  }): object;
52
55
  /**
53
56
  * Resolve one infobox reference against a site index.
@@ -47,6 +47,26 @@ export function schemaSubtypeOf(system: string | undefined, type: string): strin
47
47
  * @type {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]}
48
48
  */
49
49
  export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.mjs").DocumentSubtypeMap[];
50
+ /**
51
+ * The map a pack declaring no `system:` compiles its documents against.
52
+ *
53
+ * A pack names the system its documents are shaped for, and one declaring none
54
+ * is compiled by the fallback pass for its document type — a single-system
55
+ * package's ordinary arrangement, and the one `sohl-thalorna` ships. That pass
56
+ * follows one map whatever a note carries, which makes this the answer to "a
57
+ * document compiled here belongs to *which* system?" wherever a pack has not
58
+ * said.
59
+ *
60
+ * Stated here rather than read off the compiler classes because those are not
61
+ * reachable from a leaf: `engine/item-compiler.mjs` imports the journals pass,
62
+ * which draws infoboxes, so anything the infobox reads cannot import a pass.
63
+ * `tests/default-document-subtypes.test.ts` holds this to `compilerFor`, which
64
+ * is where the fallback is actually decided — so the two cannot drift apart in
65
+ * silence.
66
+ *
67
+ * @type {import("./document-subtypes.mjs").DocumentSubtypeMap}
68
+ */
69
+ export const DEFAULT_DOCUMENT_SUBTYPES: import("./document-subtypes.mjs").DocumentSubtypeMap;
50
70
  /**
51
71
  * Every note type any shipped map compiles into an **Actor**.
52
72
  *
@@ -66,15 +66,25 @@ export function frontmatterWikilinks(fm: unknown): Array<{
66
66
  * web pages, so the author wrote a real address and there is simply nothing to
67
67
  * link to.
68
68
  *
69
+ * ## Embeds first, and they are not links
70
+ *
71
+ * `![[address|label]]` names a file rather than a note, so it is resolved into
72
+ * an ordinary markdown image before anything looks for a link — which is what
73
+ * stops the link pass reading an embed's interior as a link to a note nobody
74
+ * wrote. The image the rewrite leaves behind is what
75
+ * {@link module:engine/content-images.renderImageFigures} turns into a figure
76
+ * for the website, and what the book reads its staging list out of.
77
+ *
69
78
  * @param {string} body - The markdown body.
70
- * @param {object} ctx - `{ index, collide, sections, contentTypes, packages,
71
- * foreign, type, errors, src, file }`.
79
+ * @param {object} ctx - `{ index, assets, collide, sections, contentTypes,
80
+ * packages, foreign, type, errors, src, file }`.
72
81
  * `packages` is every package an address may name, without which the leading
73
82
  * package segment of a canonical address reads as an unknown type; `foreign`
74
- * is the cross-package manifest index. `src` is the page's display
83
+ * is the cross-package manifest index; `assets` is the address space an embed
84
+ * resolves against. `src` is the page's display
75
85
  * path and `file` the source file a diagnostic should name — absent, `src`
76
86
  * stands in.
77
- * @returns {string} The body with wikilinks rewritten.
87
+ * @returns {string} The body with embeds and wikilinks rewritten.
78
88
  */
79
89
  export function resolveWebWikilinks(body: string, ctx: object): string;
80
90
  export { slugify };
@@ -128,15 +128,18 @@ export function ambiguousAddressMessage(target: string, packages?: Iterable<stri
128
128
  * @param {Iterable<string>} [finding.packages] - For `ambiguous`, the
129
129
  * claimants.
130
130
  * @param {string} [finding.anchor] - For `unknown-anchor`, the section named.
131
+ * @param {string} [finding.type] - For `not-an-asset`, the type the address
132
+ * named.
131
133
  * @returns {string} The message.
132
134
  * @throws {Error} On a reason outside the closed set — a resolver inventing one
133
135
  * would otherwise report a link with no explanation at all.
134
136
  */
135
- export function linkFindingMessage({ reason, target, packages, anchor }: {
137
+ export function linkFindingMessage({ reason, target, packages, anchor, type }: {
136
138
  reason: string;
137
139
  target: string;
138
140
  packages?: Iterable<string> | undefined;
139
141
  anchor?: string | undefined;
142
+ type?: string | undefined;
140
143
  }): string;
141
144
  /**
142
145
  * Whether a parsed link addresses a section of the page it is written on.
@@ -193,6 +196,13 @@ export function isSamePage({ target, anchor }: ParsedWikilink): boolean;
193
196
  * unclosed `[[` is a typo, and the alternative is letting it swallow arbitrary
194
197
  * prose in search of a closer. Erring towards "not a link" leaves the author's
195
198
  * text as written, which is the safe direction for a rewriter.
199
+ *
200
+ * **A leading `!` makes it an embed, not a link**, and the lookbehind is what
201
+ * keeps every reader from seeing one as the other. Stated here rather than in
202
+ * each reader for the reason the rest of this module exists: the checker, the
203
+ * pack compilers and the web resolver all match on this pattern, and three
204
+ * copies of the exclusion is three places for it to be forgotten. An embed's
205
+ * own grammar is {@link module:engine/content-embeds}.
196
206
  */
197
207
  export const WIKILINK: RegExp;
198
208
  /**
@@ -207,7 +217,9 @@ export const WIKILINK: RegExp;
207
217
  *
208
218
  * - `unlabelled` — no `|`, so the link addresses nothing.
209
219
  * - `not-an-address` — labelled, but the target does not parse as an address.
220
+ * - `not-lowercase` — a package, system or type segment carries a capital.
210
221
  * - `unknown-type` — definitely qualified, but names no type this build knows.
222
+ * - `not-an-asset` — a real address, embedded where only a file can be drawn.
211
223
  * - `unresolved` — parses as an address, and nothing publishes it.
212
224
  * - `ambiguous` — more than one package publishes the short address.
213
225
  * - `unknown-anchor` — the address resolved, the `#section` it names did not.