@heroiclands/package-build 20.4.0 → 20.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +288 -0
- package/CONTENT.md +213 -20
- package/README.md +19 -1
- package/bin/content-build.mjs +135 -32
- package/bin/package-build.mjs +46 -13
- package/content-config.mjs +345 -101
- package/docs/api.md +1352 -0
- package/docs/commands.md +1609 -0
- package/docs/configuration.md +1432 -0
- package/docs/content-format.md +16 -6
- package/docs/diagnostics.md +356 -0
- package/docs/getting-started.md +813 -0
- package/docs/project-setup.md +469 -0
- package/engine/actor-compiler.mjs +30 -27
- package/engine/address-diff.mjs +45 -41
- package/engine/base-compiler.mjs +6 -0
- package/engine/bundles.mjs +9 -0
- package/engine/content-address.mjs +9 -9
- package/engine/content-index.mjs +44 -23
- package/engine/content-links.mjs +44 -11
- package/engine/content-lint.mjs +44 -10
- package/engine/content-tables.mjs +32 -27
- package/engine/folder-notes.mjs +4 -2
- package/engine/frontmatter-lint.mjs +35 -38
- package/engine/generate.mjs +5 -0
- package/engine/helpers.mjs +86 -32
- package/engine/index.mjs +12 -2
- package/engine/journals.mjs +9 -0
- package/engine/note-claims.mjs +18 -10
- package/engine/note-schemas.mjs +0 -5
- package/engine/note-vocabulary.mjs +32 -31
- package/engine/pack-config.mjs +26 -12
- package/engine/pack-router.mjs +0 -0
- package/engine/pdf-build.mjs +464 -0
- package/engine/pdf-fonts.mjs +420 -0
- package/engine/pdf-render.mjs +876 -0
- package/engine/pdf-toc.mjs +525 -0
- package/engine/scenes.mjs +14 -5
- package/engine/schema-check.mjs +1 -1
- package/engine/site-build.mjs +21 -3
- package/engine/web-wikilinks.mjs +6 -3
- package/engine/wikilinks.mjs +2 -4
- package/hm3/actors.mjs +8 -0
- package/hm3/items.mjs +8 -0
- package/package.json +1 -1
- package/release.mjs +63 -3
- package/sohl/actors.mjs +8 -0
- package/sohl/items.mjs +8 -0
- package/sohl/note-schemas.mjs +5 -5
- package/types/content-config.d.mts +66 -15
- package/types/engine/actor-compiler.d.mts +34 -30
- package/types/engine/address-diff.d.mts +57 -3
- package/types/engine/base-compiler.d.mts +10 -2
- package/types/engine/bundles.d.mts +9 -0
- package/types/engine/content-address.d.mts +9 -9
- package/types/engine/content-index.d.mts +57 -13
- package/types/engine/content-lint.d.mts +6 -4
- package/types/engine/content-tables.d.mts +49 -18
- package/types/engine/frontmatter-lint.d.mts +3 -2
- package/types/engine/helpers.d.mts +105 -31
- package/types/engine/index.d.mts +4 -0
- package/types/engine/journals.d.mts +9 -0
- package/types/engine/note-claims.d.mts +17 -10
- package/types/engine/note-vocabulary.d.mts +23 -196
- package/types/engine/pack-config.d.mts +4 -4
- package/types/engine/pdf-build.d.mts +42 -0
- package/types/engine/pdf-fonts.d.mts +30 -0
- package/types/engine/pdf-render.d.mts +156 -0
- package/types/engine/pdf-toc.d.mts +114 -0
- package/types/engine/scenes.d.mts +10 -1
- package/types/engine/schema-check.d.mts +2 -2
- package/types/engine/site-build.d.mts +34 -6
- package/types/engine/wikilinks.d.mts +2 -3
- package/types/hm3/actors.d.mts +8 -0
- package/types/hm3/items.d.mts +8 -0
- package/types/release.d.mts +15 -4
- package/types/sohl/actors.d.mts +10 -2
- package/types/sohl/items.d.mts +8 -0
package/hm3/actors.mjs
CHANGED
|
@@ -189,6 +189,14 @@ function defaultActorImg(subType) {
|
|
|
189
189
|
return img;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* HM3's Actor compile pass.
|
|
194
|
+
*
|
|
195
|
+
* Declares HM3's note-type → document-subtype map and builds a `being` note's
|
|
196
|
+
* document: the subtype the note authors in `hm3.type`, the mapped fields and
|
|
197
|
+
* the two anchored prose sections, and the embedded items `hm3.items` names.
|
|
198
|
+
* Everything else is {@link module:engine/actor-compiler}'s.
|
|
199
|
+
*/
|
|
192
200
|
export class Hm3Actors extends SystemActorCompiler {
|
|
193
201
|
/**
|
|
194
202
|
* HM3's note-type → document-subtype map — the one declaration that says
|
package/hm3/items.mjs
CHANGED
|
@@ -54,6 +54,14 @@ import { SystemItemCompiler } from "../engine/item-compiler.mjs";
|
|
|
54
54
|
import { HM3_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
55
55
|
import { templateFlags } from "./template-priority.mjs";
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* HM3's Item compile pass.
|
|
59
|
+
*
|
|
60
|
+
* Declares HM3's note-type → document-subtype map, which decides the notes this
|
|
61
|
+
* pass claims and what each becomes, the one `system` key HM3 writes from prose,
|
|
62
|
+
* and the template-priority flag HM3's data model has no field for. Everything
|
|
63
|
+
* else is {@link module:engine/item-compiler}'s.
|
|
64
|
+
*/
|
|
57
65
|
export class Hm3Items extends SystemItemCompiler {
|
|
58
66
|
/**
|
|
59
67
|
* HM3's note-type → document-subtype map — the one declaration that says
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.6.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
package/release.mjs
CHANGED
|
@@ -57,10 +57,17 @@ import { ZipArchive } from "archiver";
|
|
|
57
57
|
* Determines both asset names.
|
|
58
58
|
* @param {string} [opts.metadataDir] - Where the build writes its content
|
|
59
59
|
* index, consulted when the advertised file was not staged.
|
|
60
|
+
* @param {boolean} [opts.pdf] - Whether to build the book that ships beside the
|
|
61
|
+
* archive. `true` by default; `false` skips the build and reports the skip.
|
|
60
62
|
* @returns {Promise<{zip: string, manifest: string, metadata?: string,
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
63
|
+
* pdf?: string, pdfFindings: object[], pdfSkipped: string|null,
|
|
64
|
+
* bytes: number, version: string}>} The paths written, what the book build
|
|
65
|
+
* found, the archive's size, and the version the manifest declares.
|
|
66
|
+
* `metadata` is absent when the manifest advertises no content index, and
|
|
67
|
+
* `pdf` is absent when no book was written. `pdfSkipped` is `null` when a
|
|
68
|
+
* book was built, and otherwise the reason none was — itself `null` when the
|
|
69
|
+
* book builder could not be loaded, which is reported through
|
|
70
|
+
* `pdfFindings`.
|
|
64
71
|
* @throws {Error} When the stage has no manifest — there is nothing to release,
|
|
65
72
|
* and an archive without one installs as nothing.
|
|
66
73
|
*/
|
|
@@ -69,6 +76,7 @@ export async function packRelease({
|
|
|
69
76
|
outDir = "build/dist",
|
|
70
77
|
artifact = "system",
|
|
71
78
|
metadataDir = "build/content-index",
|
|
79
|
+
pdf = true,
|
|
72
80
|
} = {}) {
|
|
73
81
|
const stage = path.resolve(stageDir);
|
|
74
82
|
const out = path.resolve(outDir);
|
|
@@ -112,15 +120,67 @@ export async function packRelease({
|
|
|
112
120
|
|
|
113
121
|
const metadata = await publishMetadataIndex({ manifest, stage, out, metadataDir });
|
|
114
122
|
|
|
123
|
+
// Last, and never fatal: the archive and the manifest are the release, and
|
|
124
|
+
// a book that failed to set is a reported problem rather than a reason to
|
|
125
|
+
// publish neither.
|
|
126
|
+
const book =
|
|
127
|
+
pdf ?
|
|
128
|
+
await packReleasePdf({ out, version: manifest.version })
|
|
129
|
+
: { pdf: null, findings: [], reason: "the release was asked not to build one" };
|
|
130
|
+
|
|
115
131
|
return {
|
|
116
132
|
zip: zipPath,
|
|
117
133
|
manifest: path.join(out, manifestName),
|
|
118
134
|
...(metadata ? { metadata } : {}),
|
|
135
|
+
...(book.pdf ? { pdf: book.pdf } : {}),
|
|
136
|
+
pdfFindings: book.findings,
|
|
137
|
+
pdfSkipped: book.pdf ? null : book.reason,
|
|
119
138
|
bytes: archive.pointer(),
|
|
120
139
|
version: manifest.version,
|
|
121
140
|
};
|
|
122
141
|
}
|
|
123
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Build the book that ships beside the archive.
|
|
145
|
+
*
|
|
146
|
+
* **Imported when it is used, not when this module is.** The book build pulls
|
|
147
|
+
* in a markdown parser, DuckDB and the whole content engine; `release.mjs`
|
|
148
|
+
* otherwise exists to zip a directory, and every consumer that publishes no
|
|
149
|
+
* book would pay for that graph on `import`. A dynamic import inside the one
|
|
150
|
+
* function that needs it keeps the cost where the benefit is.
|
|
151
|
+
*
|
|
152
|
+
* **Not building is the normal case and never an error.** A package publishing
|
|
153
|
+
* only a homepage, one with no `pdf:` block and one with no content tree have
|
|
154
|
+
* each said they publish no book. Four of the six packages that install this
|
|
155
|
+
* toolchain are in exactly that position, so a release that failed for the
|
|
156
|
+
* absence of a PDF would break more releases than it helped.
|
|
157
|
+
*
|
|
158
|
+
* @param {object} opts - Options.
|
|
159
|
+
* @param {string} opts.out - The release directory.
|
|
160
|
+
* @param {string} opts.version - The version the manifest declares.
|
|
161
|
+
* @returns {Promise<{pdf: string|null, findings: object[], reason: string|null}>}
|
|
162
|
+
* What was built, and what was found on the way.
|
|
163
|
+
*/
|
|
164
|
+
async function packReleasePdf({ out, version }) {
|
|
165
|
+
let buildPdf;
|
|
166
|
+
try {
|
|
167
|
+
({ buildPdf } = await import("./engine/pdf-build.mjs"));
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
pdf: null,
|
|
171
|
+
findings: [
|
|
172
|
+
{
|
|
173
|
+
severity: "warning",
|
|
174
|
+
message: `the book builder could not be loaded: ${err.message}`,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
reason: null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const result = await buildPdf({ out, version });
|
|
181
|
+
return { pdf: result.pdf, findings: result.findings, reason: result.reason };
|
|
182
|
+
}
|
|
183
|
+
|
|
124
184
|
/**
|
|
125
185
|
* Place the content index the manifest advertises beside the archive.
|
|
126
186
|
*
|
package/sohl/actors.mjs
CHANGED
|
@@ -190,6 +190,14 @@ function extractBodyAndMovement(fm) {
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* SoHL's Actor compile pass.
|
|
195
|
+
*
|
|
196
|
+
* Declares SoHL's note-type → document-subtype map and builds a `being` note's
|
|
197
|
+
* document: the body structure and its movement profiles, the embedded
|
|
198
|
+
* attribute and item documents the frontmatter names, and the `system` block.
|
|
199
|
+
* Everything else is {@link module:engine/actor-compiler}'s.
|
|
200
|
+
*/
|
|
193
201
|
export class Actors extends SystemActorCompiler {
|
|
194
202
|
/**
|
|
195
203
|
* SoHL's note-type → document-subtype map — the one declaration that says
|
package/sohl/items.mjs
CHANGED
|
@@ -41,6 +41,14 @@ import { systemTemplatePriority } from "../engine/helpers.mjs";
|
|
|
41
41
|
import { SystemItemCompiler } from "../engine/item-compiler.mjs";
|
|
42
42
|
import { SOHL_DOCUMENT_SUBTYPES } from "./document-subtypes.mjs";
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* SoHL's Item compile pass.
|
|
46
|
+
*
|
|
47
|
+
* Declares SoHL's note-type → document-subtype map, which decides the notes
|
|
48
|
+
* this pass claims and what each becomes, and the `system` keys SoHL writes on
|
|
49
|
+
* every item whatever its type. Everything else is
|
|
50
|
+
* {@link module:engine/item-compiler}'s.
|
|
51
|
+
*/
|
|
44
52
|
export class Items extends SystemItemCompiler {
|
|
45
53
|
/**
|
|
46
54
|
* SoHL's note-type → document-subtype map — the one declaration that says
|
package/sohl/note-schemas.mjs
CHANGED
|
@@ -82,11 +82,6 @@ const MACRO_FIELDS = Object.freeze([
|
|
|
82
82
|
},
|
|
83
83
|
]);
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
* A `being` note — an actor, with its body, movement and embedded items.
|
|
87
|
-
*
|
|
88
|
-
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
89
|
-
*/
|
|
90
85
|
/**
|
|
91
86
|
* A `vehicle` note — a conveyance that carries goods and people.
|
|
92
87
|
*
|
|
@@ -103,6 +98,11 @@ const MACRO_FIELDS = Object.freeze([
|
|
|
103
98
|
*/
|
|
104
99
|
const VEHICLE_FIELDS = Object.freeze([]);
|
|
105
100
|
|
|
101
|
+
/**
|
|
102
|
+
* A `being` note — an actor, with its body, movement and embedded items.
|
|
103
|
+
*
|
|
104
|
+
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
105
|
+
*/
|
|
106
106
|
const BEING_FIELDS = Object.freeze([
|
|
107
107
|
{
|
|
108
108
|
name: "body",
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether this package compiles Foundry documents at all.
|
|
3
|
+
*
|
|
4
|
+
* The one question every Foundry-side reader asks — the manifest writer, to
|
|
5
|
+
* decide whether there is a package for Foundry to install, and the pack
|
|
6
|
+
* compilers, to decide whether there is anything to compile.
|
|
7
|
+
*
|
|
8
|
+
* @param {{packageKind: string}} config - A resolved configuration.
|
|
9
|
+
* @returns {boolean} Whether the package compiles Foundry documents.
|
|
10
|
+
*/
|
|
11
|
+
export function compilesFoundryDocuments(config: {
|
|
12
|
+
packageKind: string;
|
|
13
|
+
}): boolean;
|
|
1
14
|
/**
|
|
2
15
|
* Whether this package publishes the pages its content tree compiles to.
|
|
3
16
|
*
|
|
@@ -30,13 +43,27 @@ export function publishesContentPages(config: {
|
|
|
30
43
|
*/
|
|
31
44
|
export function defineConfig(config: ContentBuildConfigInput): ContentBuildConfig;
|
|
32
45
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
46
|
+
* What kind of package this is.
|
|
47
|
+
*
|
|
48
|
+
* `systems` and `modules` are the two Foundry answers, and the value is also
|
|
49
|
+
* the directory Foundry installs the package under, which is why they are
|
|
50
|
+
* plural. `documentation` is the answer "not a Foundry package at all": it
|
|
51
|
+
* publishes a site and a book from its notes, installs into no Foundry data
|
|
52
|
+
* directory and compiles no compendium.
|
|
36
53
|
*
|
|
37
54
|
* @satisfies {readonly PackageKind[]}
|
|
38
55
|
*/
|
|
39
|
-
export const PACKAGE_KINDS: readonly ["systems", "modules"];
|
|
56
|
+
export const PACKAGE_KINDS: readonly ["systems", "modules", "documentation"];
|
|
57
|
+
/**
|
|
58
|
+
* The kind that compiles no Foundry documents.
|
|
59
|
+
*
|
|
60
|
+
* Spelled once and read wherever a pass asks whether it applies, so the
|
|
61
|
+
* validator, the CLI and the compile passes cannot come to disagree about what
|
|
62
|
+
* the value means.
|
|
63
|
+
*
|
|
64
|
+
* @type {string}
|
|
65
|
+
*/
|
|
66
|
+
export const DOCUMENTATION_KIND: string;
|
|
40
67
|
export namespace DEFAULT_PATHS {
|
|
41
68
|
let content: "assets/content";
|
|
42
69
|
let contentIndex: "build/content-index";
|
|
@@ -179,7 +206,7 @@ export const DERIVED_SYSTEM_VERSION: symbol;
|
|
|
179
206
|
* means what it said.
|
|
180
207
|
*/
|
|
181
208
|
export type SiteMode = "homepage" | "content";
|
|
182
|
-
export type PackageKind = "systems" | "modules";
|
|
209
|
+
export type PackageKind = "systems" | "modules" | "documentation";
|
|
183
210
|
export type PackDocumentType = "Actor" | "Adventure" | "Item" | "JournalEntry" | "Macro" | "Scene";
|
|
184
211
|
/**
|
|
185
212
|
* One compendium pack the build compiles, named exactly as it is declared in
|
|
@@ -542,17 +569,27 @@ export type ContentBuildConfigInput = {
|
|
|
542
569
|
contentPackage: string;
|
|
543
570
|
/**
|
|
544
571
|
* Foundry package id, as it appears in
|
|
545
|
-
*
|
|
572
|
+
* `system.json` / `module.json`.
|
|
573
|
+
* Refused by a `documentation`
|
|
574
|
+
* package, which ships no Foundry
|
|
575
|
+
* package.
|
|
546
576
|
*/
|
|
547
|
-
foundryPackage
|
|
577
|
+
foundryPackage?: string | undefined;
|
|
548
578
|
/**
|
|
549
|
-
* Whether the package is a system
|
|
579
|
+
* Whether the package is a system, a
|
|
580
|
+
* module, or documentation — the kind
|
|
581
|
+
* that publishes a site and a book
|
|
582
|
+
* while compiling nothing.
|
|
550
583
|
*/
|
|
551
584
|
packageKind: PackageKind;
|
|
552
585
|
/**
|
|
553
|
-
* Identity stamped into every
|
|
586
|
+
* Identity stamped into every
|
|
587
|
+
* document's `_stats`. Required of a
|
|
588
|
+
* package that compiles documents, and
|
|
589
|
+
* refused by a `documentation` one,
|
|
590
|
+
* which compiles none.
|
|
554
591
|
*/
|
|
555
|
-
stats
|
|
592
|
+
stats?: StatsSpec | undefined;
|
|
556
593
|
/**
|
|
557
594
|
* The consumer's
|
|
558
595
|
* item-type registry: each content `type`
|
|
@@ -626,15 +663,29 @@ export type ContentBuildConfigInput = {
|
|
|
626
663
|
export type ContentBuildConfig = {
|
|
627
664
|
rootDir: string;
|
|
628
665
|
contentPackage: string;
|
|
629
|
-
|
|
666
|
+
/**
|
|
667
|
+
* `null` for a `documentation`
|
|
668
|
+
* package, which ships no Foundry package.
|
|
669
|
+
*/
|
|
670
|
+
foundryPackage: string | null;
|
|
630
671
|
packageKind: PackageKind;
|
|
631
672
|
/**
|
|
632
|
-
* Derived
|
|
633
|
-
*
|
|
673
|
+
* Derived, and **conditional**: the served
|
|
674
|
+
* Foundry asset root,
|
|
675
|
+
* `<packageKind>/<foundryPackage>/assets`,
|
|
676
|
+
* for a package Foundry installs — and
|
|
677
|
+
* `null` for a `documentation` package,
|
|
678
|
+
* which Foundry serves no files for. See
|
|
679
|
+
* {@link module :engine/helpers.resolveImg},
|
|
680
|
+
* the one reader of it.
|
|
634
681
|
*/
|
|
635
|
-
assetRoot: string;
|
|
682
|
+
assetRoot: string | null;
|
|
636
683
|
paths: Readonly<ResolvedPaths>;
|
|
637
|
-
|
|
684
|
+
/**
|
|
685
|
+
* `null` for a `documentation`
|
|
686
|
+
* package, which stamps no `_stats`.
|
|
687
|
+
*/
|
|
688
|
+
stats: Readonly<StatsSpec> | null;
|
|
638
689
|
/**
|
|
639
690
|
* Derived: the
|
|
640
691
|
* `system` builder of each entry, whichever
|
|
@@ -224,33 +224,6 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
224
224
|
* subtype, or why the reference names none.
|
|
225
225
|
*/
|
|
226
226
|
embeddedSubtype(type: string): import("./document-subtypes.mjs").ReferencedSubtype;
|
|
227
|
-
/**
|
|
228
|
-
* Resolve one embedded item from a `(type, shortcode?, overlay)`
|
|
229
|
-
* descriptor. If `shortcode` is given, the predefined item is fetched
|
|
230
|
-
* from `itemsMap` and the overlay deep-merged on top. If absent, the
|
|
231
|
-
* descriptor must carry enough fields to stand alone. The embedded
|
|
232
|
-
* item's `_id` is regenerated deterministically from
|
|
233
|
-
* `(actorId, subType, shortcode, indexKey)` so re-exports are stable —
|
|
234
|
-
* from the **document subtype**, so that renaming a note type leaves
|
|
235
|
-
* every embedded id exactly where it was.
|
|
236
|
-
* Returns null if the descriptor cannot be resolved.
|
|
237
|
-
*
|
|
238
|
-
* @param {Map<string, object>} itemsMap - The predefined items, by address.
|
|
239
|
-
* @param {string} actorId - The owning actor's id, seeding embedded ids.
|
|
240
|
-
* @param {string} type - The **note** type the reference names.
|
|
241
|
-
* @param {string|null} shortcode - The referenced item's shortcode, or
|
|
242
|
-
* `null` for a stand-alone entry.
|
|
243
|
-
* @param {object} [overlay] - The entry's remaining properties.
|
|
244
|
-
* @param {string} indexKey - Where the reference sits, for a diagnostic.
|
|
245
|
-
* It no longer reaches the id — it names the entry in a message.
|
|
246
|
-
* @param {string} ctx - Diagnostic context (the actor's label).
|
|
247
|
-
* @param {object} [at] - Where to locate a finding.
|
|
248
|
-
* @param {string} [at.fmKey] - The frontmatter key the reference sits
|
|
249
|
-
* under, so an unresolved one is reported at the reference rather than
|
|
250
|
-
* at the note.
|
|
251
|
-
* @returns {object|null} The embedded item, or null when it resolved to
|
|
252
|
-
* nothing — always with a finding emitted.
|
|
253
|
-
*/
|
|
254
227
|
/**
|
|
255
228
|
* Read an entry's `model:` — the address of the item it is a copy of.
|
|
256
229
|
*
|
|
@@ -276,9 +249,40 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
276
249
|
shortcode: string;
|
|
277
250
|
package: string | null;
|
|
278
251
|
} | null;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Resolve one embedded item from a `(type, shortcode?, overlay)`
|
|
254
|
+
* descriptor. If `shortcode` is given, the predefined item is fetched
|
|
255
|
+
* from `itemsMap` and the overlay deep-merged on top. If absent, the
|
|
256
|
+
* descriptor must carry enough fields to stand alone. The embedded
|
|
257
|
+
* item's `_id` is regenerated deterministically from
|
|
258
|
+
* `(actorId, subType, shortcode, indexKey)` so re-exports are stable —
|
|
259
|
+
* from the **document subtype**, so that renaming a note type leaves
|
|
260
|
+
* every embedded id exactly where it was.
|
|
261
|
+
* Returns null if the descriptor cannot be resolved.
|
|
262
|
+
*
|
|
263
|
+
* @param {Map<string, object>} itemsMap - The predefined items, by address.
|
|
264
|
+
* @param {string} actorId - The owning actor's id, seeding embedded ids.
|
|
265
|
+
* @param {string} type - The **note** type the reference names.
|
|
266
|
+
* @param {string|null} shortcode - The referenced item's shortcode, or
|
|
267
|
+
* `null` for a stand-alone entry.
|
|
268
|
+
* @param {object} overlay - The entry's remaining properties.
|
|
269
|
+
* @param {string} indexKey - Where the reference sits, for a diagnostic.
|
|
270
|
+
* It names the entry in a message and does not reach the id.
|
|
271
|
+
* @param {string} ctx - Diagnostic context (the actor's label).
|
|
272
|
+
* @param {object} [at] - Where to locate a finding.
|
|
273
|
+
* @param {string} [at.fmKey] - The frontmatter key the reference sits
|
|
274
|
+
* under, so an unresolved one is reported at the reference rather than
|
|
275
|
+
* at the note.
|
|
276
|
+
* @param {string|null} [at.modelPackage] - The package a `model:` names,
|
|
277
|
+
* where the template comes from another package's catalogue. Given one,
|
|
278
|
+
* the packaged address is what resolves and nothing local shadows it.
|
|
279
|
+
* @returns {object|null} The embedded item, or null when it resolved to
|
|
280
|
+
* nothing — always with a finding emitted.
|
|
281
|
+
*/
|
|
282
|
+
resolveEmbedded(itemsMap: Map<string, object>, actorId: string, type: string, shortcode: string | null, overlay: object, indexKey: string, ctx: string, { fmKey, modelPackage }?: {
|
|
283
|
+
fmKey?: string | undefined;
|
|
284
|
+
modelPackage?: string | null | undefined;
|
|
285
|
+
}): object | null;
|
|
282
286
|
#private;
|
|
283
287
|
}
|
|
284
288
|
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
@@ -39,9 +39,60 @@ export function readItemAddresses(dirs: readonly string[]): Map<string, {
|
|
|
39
39
|
shortcode: string;
|
|
40
40
|
file: string;
|
|
41
41
|
}>;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Every rename the tree's notes **declare**, as old address → where it went.
|
|
44
|
+
*
|
|
45
|
+
* Read from the content tree rather than from compiled output, because a
|
|
46
|
+
* declaration is authored and the compiled document does not carry it: nothing
|
|
47
|
+
* downstream consumes `renamedFrom:`, so emitting it into every pack to let one
|
|
48
|
+
* diagnostic read it back would put a build-time note in shipped data forever.
|
|
49
|
+
* The tree is already read by this module for the same reason
|
|
50
|
+
* ({@link noteFilesById}) — to place a finding where its author can fix it.
|
|
51
|
+
*
|
|
52
|
+
* **A declaration is keyed by document subtype, not by note type.** The address
|
|
53
|
+
* space is the one consumers resolve against, and it is spelled in compiled
|
|
54
|
+
* documents: `hm3` compiles a `projectile` note into a `missilegear` item,
|
|
55
|
+
* so that is the address a rename of it moves. {@link referencedSubtype} is the
|
|
56
|
+
* function that already answers this for a being's embedded `(type, shortcode)`
|
|
57
|
+
* references, so both sides read the same rule rather than a second copy of it.
|
|
58
|
+
*
|
|
59
|
+
* **An entry is emitted for every system that maps the type**, whether or not
|
|
60
|
+
* the note declares that system's block. Over-emitting is inert — the diff uses
|
|
61
|
+
* an entry only when the baseline published the old address *and* this build
|
|
62
|
+
* publishes the new one, and a system the note does not compile for satisfies
|
|
63
|
+
* neither — while asking which blocks a note declares would put a second,
|
|
64
|
+
* subtly different answer to that question in a third place.
|
|
65
|
+
*
|
|
66
|
+
* **First claim wins on a collision.** Two notes naming one predecessor is a
|
|
67
|
+
* contradiction — an address has one successor — and it is reported as an error
|
|
68
|
+
* by `engine/content-lint.mjs`, where both notes are in hand and can both be
|
|
69
|
+
* named. Picking one here keeps this a map; it is not a resolution, and nothing
|
|
70
|
+
* rests on which one it picked.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} contentBase - Root of the content tree.
|
|
73
|
+
* @param {object} opts
|
|
74
|
+
* @param {readonly string[]} [opts.skipDirectories] - The corpus scope. Stated
|
|
75
|
+
* by the caller, never defaulted — see {@link addressCorpus}.
|
|
76
|
+
* @param {readonly object[]} [opts.maps] - The document-subtype maps.
|
|
77
|
+
* @param {object} [opts.config] - The resolved build configuration.
|
|
78
|
+
* @param {readonly object[]} [opts.records] - Index records the caller already
|
|
79
|
+
* derived, shared with {@link noteFilesById} so one command reads one corpus.
|
|
80
|
+
* @param {object[]} [opts.problems] - Collects the notes the index cannot
|
|
81
|
+
* record, so one of them does not abort the diff before it reports.
|
|
82
|
+
* @returns {Map<string, {to: string, file: string, shortcode: string}>} Old
|
|
83
|
+
* address → the address the declaring note publishes at now, and that note.
|
|
84
|
+
*/
|
|
85
|
+
export function declaredPredecessors(contentBase: string, { skipDirectories, maps, config, records, problems }?: {
|
|
86
|
+
skipDirectories?: readonly string[] | undefined;
|
|
87
|
+
maps?: readonly object[] | undefined;
|
|
88
|
+
config?: object | undefined;
|
|
89
|
+
records?: readonly object[] | undefined;
|
|
90
|
+
problems?: object[] | undefined;
|
|
91
|
+
}): Map<string, {
|
|
92
|
+
to: string;
|
|
93
|
+
file: string;
|
|
94
|
+
shortcode: string;
|
|
95
|
+
}>;
|
|
45
96
|
/**
|
|
46
97
|
* Every address the baseline published that this build does not.
|
|
47
98
|
*
|
|
@@ -85,12 +136,15 @@ export function diffItemAddresses(baseline: Map<string, object>, current: Map<st
|
|
|
85
136
|
* id is derived against. See {@link addressCorpus} for why that matters.
|
|
86
137
|
* @param {readonly object[]} [opts.records] - Index records the caller already
|
|
87
138
|
* derived, shared with {@link declaredPredecessors}.
|
|
139
|
+
* @param {object[]} [opts.problems] - Collects the notes the index cannot
|
|
140
|
+
* record, so one of them does not abort the diff before it reports.
|
|
88
141
|
* @returns {Map<string, string>} Document id → the note's absolute path.
|
|
89
142
|
*/
|
|
90
143
|
export function noteFilesById(contentBase: string, { skipDirectories, config, records, problems }?: {
|
|
91
144
|
skipDirectories?: readonly string[] | undefined;
|
|
92
145
|
config?: object | undefined;
|
|
93
146
|
records?: readonly object[] | undefined;
|
|
147
|
+
problems?: object[] | undefined;
|
|
94
148
|
}): Map<string, string>;
|
|
95
149
|
/**
|
|
96
150
|
* Where to send the reader for one finding.
|
|
@@ -136,11 +136,17 @@ export class BasePackCompiler {
|
|
|
136
136
|
* Resolves a `packFolder` — a folder note's address — to the Foundry
|
|
137
137
|
* folder id it materialises as in this pack.
|
|
138
138
|
* @param {string} [options.packName] - The pack this pass writes.
|
|
139
|
+
* @param {string|null} [options.packSystem] - The system that pack declares,
|
|
140
|
+
* which decides the `_stats` it stamps and the system block a note must
|
|
141
|
+
* carry to reach it. `null` for a pack that declares none.
|
|
139
142
|
* @param {string} [options.docType] - The Foundry document type it holds.
|
|
140
143
|
* @param {{resolve: Function}} [options.router] - The pack router. Omit it
|
|
141
144
|
* — as the unit suite does — and every claimed note is compiled here.
|
|
142
145
|
* @param {boolean} [options.routingReporter] - Whether this pass reports a
|
|
143
146
|
* note of its type that routes nowhere.
|
|
147
|
+
* @param {object} [options.corpus] - The corpus this compile is running
|
|
148
|
+
* over, derived once and shared by every pass. A pass handed none derives
|
|
149
|
+
* its own in `prepare`, and reports that corpus's problems itself.
|
|
144
150
|
*/
|
|
145
151
|
constructor({ contentBase, dest, skipDirectories, folderResolver, packName, packSystem, docType, router, routingReporter, corpus, }?: {
|
|
146
152
|
contentBase: string;
|
|
@@ -148,11 +154,13 @@ export class BasePackCompiler {
|
|
|
148
154
|
skipDirectories: readonly string[];
|
|
149
155
|
folderResolver?: ((address: string | null) => string | null) | undefined;
|
|
150
156
|
packName?: string | undefined;
|
|
157
|
+
packSystem?: string | null | undefined;
|
|
151
158
|
docType?: string | undefined;
|
|
152
159
|
router?: {
|
|
153
160
|
resolve: Function;
|
|
154
161
|
} | undefined;
|
|
155
162
|
routingReporter?: boolean | undefined;
|
|
163
|
+
corpus?: object | undefined;
|
|
156
164
|
});
|
|
157
165
|
/** @type {string} */
|
|
158
166
|
contentBase: string;
|
|
@@ -219,8 +227,8 @@ export class BasePackCompiler {
|
|
|
219
227
|
* @type {number}
|
|
220
228
|
*/
|
|
221
229
|
unresolvedLinks: number;
|
|
222
|
-
packSystem:
|
|
223
|
-
corpus:
|
|
230
|
+
packSystem: string | null;
|
|
231
|
+
corpus: object | undefined;
|
|
224
232
|
/**
|
|
225
233
|
* The `_stats` block every entry this pass emits is stamped with.
|
|
226
234
|
*
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* pack, which reordering cannot fix.
|
|
21
21
|
*/
|
|
22
22
|
export function loadBundleSources(sourceDirs: Readonly<Record<string, readonly string[]>>): Map<string, object>;
|
|
23
|
+
/**
|
|
24
|
+
* Adventure pack compiler.
|
|
25
|
+
*
|
|
26
|
+
* Walks the content tree and compiles every `type: bundle` note into one
|
|
27
|
+
* Adventure per system, holding **copies** of the compiled documents its
|
|
28
|
+
* `contents` addresses name. Reading other passes' output is what
|
|
29
|
+
* {@link Bundles.readsPackOutputOf} declares, and what the generator orders the
|
|
30
|
+
* compile by.
|
|
31
|
+
*/
|
|
23
32
|
export class Bundles extends BasePackCompiler {
|
|
24
33
|
/**
|
|
25
34
|
* @param {object} options - As {@link BasePackCompiler}, plus:
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* consumer deriving a `path` from a key drops the *package and the system*, not
|
|
15
15
|
* the package alone.
|
|
16
16
|
*
|
|
17
|
-
* The hyphen is a separator and never occurs inside a segment: a shortcode
|
|
18
|
-
*
|
|
19
|
-
* and a type is a bare word. That is the same charset
|
|
20
|
-
* parsing rests on, so the address and the key
|
|
21
|
-
* ends and the other begins.
|
|
17
|
+
* The hyphen is a separator and never occurs inside a segment: a shortcode
|
|
18
|
+
* matches `ADDRESS_SEGMENT_PATTERN` (`engine/address-charset.mjs`, enforced by
|
|
19
|
+
* `content-lint.mjs`) and a type is a bare word. That is the same charset
|
|
20
|
+
* guarantee positional key parsing rests on, so the address and the key
|
|
21
|
+
* cannot disagree about where one ends and the other begins.
|
|
22
22
|
*
|
|
23
23
|
* @param {object} fm - Parsed frontmatter.
|
|
24
24
|
* @returns {string} The address segment, e.g. `weapongear-dagger`.
|
|
@@ -182,10 +182,10 @@ export function expandAddress(read: {
|
|
|
182
182
|
*
|
|
183
183
|
* Parsing is plain positional counting: split on the separator, require
|
|
184
184
|
* {@link CANONICAL_KEY_SEGMENTS} of them, and assign each position its field.
|
|
185
|
-
* **The charset rule is what makes that sound** — every segment
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
185
|
+
* **The charset rule is what makes that sound** — every segment matches
|
|
186
|
+
* `ADDRESS_SEGMENT_PATTERN` (`engine/address-charset.mjs`), so the hyphen is
|
|
187
|
+
* purely a separator and the count alone determines every field. That is
|
|
188
|
+
* enforced at each of the three sources rather than assumed of
|
|
189
189
|
* the data: shortcodes by `content-lint.mjs`, `contentPackage` by
|
|
190
190
|
* `defineConfig`, and types are bare words. Were any of them free to
|
|
191
191
|
* carry a hyphen, no amount of counting would recover the fields and the reader
|
|
@@ -91,21 +91,65 @@ export function asciiName(name: unknown): string | null;
|
|
|
91
91
|
* a consumer iterating it should not have to check first.
|
|
92
92
|
*/
|
|
93
93
|
export function asciiAliases(aliases: unknown): Array<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Build one index record from a note's frontmatter and its place in the tree.
|
|
96
|
+
*
|
|
97
|
+
* The frontmatter as authored, plus what the index derives from it: the package
|
|
98
|
+
* it compiles as, its address, ASCII folds of its name and aliases, the anchors
|
|
99
|
+
* of its body, its Foundry block, the address of its documentation journal, and
|
|
100
|
+
* where the file sits within the tree.
|
|
101
|
+
*
|
|
102
|
+
* @param {object} options - Options.
|
|
103
|
+
* @param {Record<string, any>} options.frontmatter - The note's parsed frontmatter.
|
|
104
|
+
* @param {string} options.relPath - Its path below the content root, POSIX-separated.
|
|
105
|
+
* @param {string} [options.absPath] - The file, read only on the failing path to
|
|
106
|
+
* locate the offending key.
|
|
107
|
+
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
108
|
+
* @param {string} [options.body] - The note's markdown body, for its anchors.
|
|
109
|
+
* @param {number} [options.bodyLine] - The 1-based file line the body starts on.
|
|
110
|
+
* @param {object} [options.manifest] - The package manifest, which the Foundry
|
|
111
|
+
* entries are derived against.
|
|
112
|
+
* @returns {Record<string, any>} The record, keys sorted at every depth.
|
|
113
|
+
* @throws {Error} When the note carries a key this module derives, which would
|
|
114
|
+
* otherwise be overwritten without a word. `file` and, where the file was
|
|
115
|
+
* read, `position` ride on the error.
|
|
116
|
+
*/
|
|
94
117
|
export function buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest, }: {
|
|
95
|
-
frontmatter: any
|
|
96
|
-
relPath:
|
|
97
|
-
absPath
|
|
98
|
-
contentPackage:
|
|
99
|
-
body
|
|
100
|
-
bodyLine
|
|
101
|
-
manifest
|
|
118
|
+
frontmatter: Record<string, any>;
|
|
119
|
+
relPath: string;
|
|
120
|
+
absPath?: string | undefined;
|
|
121
|
+
contentPackage: string;
|
|
122
|
+
body?: string | undefined;
|
|
123
|
+
bodyLine?: number | undefined;
|
|
124
|
+
manifest?: object | undefined;
|
|
102
125
|
}): Record<string, any>;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Read a content tree into index records, in the order they will be written.
|
|
128
|
+
*
|
|
129
|
+
* An item note yields two records — the item, and the documentation journal
|
|
130
|
+
* that is a document in its own right.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} contentBase - The content tree to walk.
|
|
133
|
+
* @param {object} options - Options.
|
|
134
|
+
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
135
|
+
* @param {readonly string[]} options.skipDirectories - The walk's scope, stated
|
|
136
|
+
* by the caller. An absent one is the caller's omission, and
|
|
137
|
+
* {@link module:engine/helpers.walkMarkdownTree} throws on it.
|
|
138
|
+
* @param {object} [options.manifest] - The package manifest, which the Foundry
|
|
139
|
+
* entries are derived against.
|
|
140
|
+
* @param {object[]} [options.problems] - Supplied by a **reader**: a note that
|
|
141
|
+
* cannot be recorded is pushed here as a diagnostic and skipped. Omitted, the
|
|
142
|
+
* note throws — the contract the emitter needs, since an index missing a note
|
|
143
|
+
* asserts that it does not exist.
|
|
144
|
+
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
145
|
+
* not depend on directory-read order.
|
|
146
|
+
*/
|
|
147
|
+
export function collectContentIndex(contentBase: string, { contentPackage, skipDirectories, manifest, problems }: {
|
|
148
|
+
contentPackage: string;
|
|
149
|
+
skipDirectories: readonly string[];
|
|
150
|
+
manifest?: object | undefined;
|
|
151
|
+
problems?: object[] | undefined;
|
|
152
|
+
}): Array<Record<string, any>>;
|
|
109
153
|
/**
|
|
110
154
|
* Serialize records as JSON Lines.
|
|
111
155
|
*
|