@heroiclands/package-build 3.2.0 → 3.3.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 +101 -0
- package/CONTENT.md +29 -4
- package/content-config.mjs +3 -3
- package/engine/base-compiler.mjs +40 -4
- package/engine/content-links.mjs +15 -15
- package/engine/content-package.mjs +18 -5
- package/engine/field-reference.mjs +3 -1
- package/engine/generate.mjs +10 -10
- package/engine/helpers.mjs +14 -6
- package/engine/index.mjs +3 -0
- package/engine/journals.mjs +2 -4
- package/engine/macros.mjs +2 -2
- package/engine/manifest-emit.mjs +10 -5
- package/engine/note-package.mjs +126 -0
- package/engine/pack-router.mjs +3 -2
- package/engine/scenes.mjs +6 -2
- package/engine/site-build.mjs +26 -7
- package/engine/site-index.mjs +8 -1
- package/package.json +1 -1
- package/sohl/actors.mjs +55 -0
- package/sohl/skill-base.mjs +280 -0
- package/types/content-config.d.mts +3 -3
- package/types/engine/base-compiler.d.mts +22 -2
- package/types/engine/content-package.d.mts +18 -5
- package/types/engine/generate.d.mts +3 -3
- package/types/engine/helpers.d.mts +6 -3
- package/types/engine/index.d.mts +1 -0
- package/types/engine/macros.d.mts +2 -2
- package/types/engine/manifest-emit.d.mts +6 -3
- package/types/engine/note-package.d.mts +54 -0
- package/types/engine/pack-router.d.mts +3 -2
- package/types/engine/site-build.d.mts +3 -1
- package/types/sohl/actors.d.mts +27 -0
- package/types/sohl/skill-base.d.mts +53 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The tallies one pass accumulates while walking the tree.
|
|
3
3
|
*
|
|
4
|
+
* `declined` and `skippedOther` are deliberately separate numbers. A declined
|
|
5
|
+
* note is one this build **refused** — it names a package this repository does
|
|
6
|
+
* not compile — and it is an error; a skipped one legitimately belongs to
|
|
7
|
+
* another pass, and there are thousands of those. Folding the first into the
|
|
8
|
+
* second is what let a whole tree be filtered out in silence (#56).
|
|
9
|
+
*
|
|
4
10
|
* @typedef {object} PassStats
|
|
5
11
|
* @property {number} compiled - Notes that became a document.
|
|
6
12
|
* @property {number} skippedDraft - Notes marked `draft: true`.
|
|
7
13
|
* @property {number} skippedNoId - Notes with no `id`, where that is tolerated.
|
|
8
14
|
* @property {number} skippedOther - Notes this pass does not claim.
|
|
15
|
+
* @property {number} declined - Notes refused because they declare another
|
|
16
|
+
* package. Counted as errors, never as skips.
|
|
9
17
|
*/
|
|
10
18
|
/**
|
|
11
19
|
* The shared walk → filter → expand → convert → build → write → count loop.
|
|
@@ -136,8 +144,9 @@ export class BasePackCompiler {
|
|
|
136
144
|
/**
|
|
137
145
|
* Whether this pass claims a note. **Required.**
|
|
138
146
|
*
|
|
139
|
-
* Called only for a note
|
|
140
|
-
*
|
|
147
|
+
* Called only for a note this build compiles — every note in the tree
|
|
148
|
+
* belongs to the configured content package (#56) — so a subclass decides
|
|
149
|
+
* on `type` alone.
|
|
141
150
|
*
|
|
142
151
|
* @param {object} fm - The note's frontmatter.
|
|
143
152
|
* @returns {boolean} True to compile it.
|
|
@@ -299,6 +308,12 @@ export class BasePackCompiler {
|
|
|
299
308
|
}
|
|
300
309
|
/**
|
|
301
310
|
* The tallies one pass accumulates while walking the tree.
|
|
311
|
+
*
|
|
312
|
+
* `declined` and `skippedOther` are deliberately separate numbers. A declined
|
|
313
|
+
* note is one this build **refused** — it names a package this repository does
|
|
314
|
+
* not compile — and it is an error; a skipped one legitimately belongs to
|
|
315
|
+
* another pass, and there are thousands of those. Folding the first into the
|
|
316
|
+
* second is what let a whole tree be filtered out in silence (#56).
|
|
302
317
|
*/
|
|
303
318
|
export type PassStats = {
|
|
304
319
|
/**
|
|
@@ -317,4 +332,9 @@ export type PassStats = {
|
|
|
317
332
|
* - Notes this pass does not claim.
|
|
318
333
|
*/
|
|
319
334
|
skippedOther: number;
|
|
335
|
+
/**
|
|
336
|
+
* - Notes refused because they declare another
|
|
337
|
+
* package. Counted as errors, never as skips.
|
|
338
|
+
*/
|
|
339
|
+
declined: number;
|
|
320
340
|
};
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The **content** package: the distribution unit
|
|
3
|
-
*
|
|
2
|
+
* The **content** package: the distribution unit this repository's notes belong
|
|
3
|
+
* to, and the **address namespace** every one of them is published under.
|
|
4
|
+
*
|
|
5
|
+
* It is the first segment of every canonical key (`sohl-skill-clmb`), the name
|
|
6
|
+
* of the link manifest this build emits (`sohl.json`), and the package a
|
|
7
|
+
* cross-package wikilink writes to reach one of these notes. So it is the
|
|
8
|
+
* repository's identity in the address space, not a switch — and never dead
|
|
9
|
+
* configuration, whatever else changes.
|
|
10
|
+
*
|
|
11
|
+
* It was also, until #56, a **selector**: a note declared the same value in its
|
|
12
|
+
* `package:` frontmatter and the compilers kept the ones that matched. Every
|
|
13
|
+
* content tree is single-package — each is single-sourced in the repository that
|
|
14
|
+
* ships it — so the field restated this constant once per note while a value
|
|
15
|
+
* that matched nothing filtered the whole tree out in silence. The field is
|
|
16
|
+
* being retired; the value stays, here, where it is declared once.
|
|
4
17
|
*
|
|
5
18
|
* Stable across compilation targets. If this content were ever compiled for a
|
|
6
|
-
* second game system,
|
|
7
|
-
*
|
|
19
|
+
* second game system, it would still be published as `sohl` — only the Foundry
|
|
20
|
+
* package below would differ.
|
|
8
21
|
*
|
|
9
22
|
* An accessor rather than a hoisted constant, so that importing this module
|
|
10
23
|
* needs no configuration (#2).
|
|
@@ -18,7 +31,7 @@ export function contentPackage(): string;
|
|
|
18
31
|
* compendium UUID the compilers emit.
|
|
19
32
|
*
|
|
20
33
|
* Distinct from {@link contentPackage}, and equal to it only by coincidence
|
|
21
|
-
* here: a note
|
|
34
|
+
* here: a note is published under `sohl` and its documents are addressed as
|
|
22
35
|
* `Compendium.sohl.<pack>.<Type>.<id>`. In `sohl-thalorna` the two differ
|
|
23
36
|
* (`thalorna` vs `sohl-thalorna`), which is why they are separate values rather
|
|
24
37
|
* than one — treating them as interchangeable is what #1498 was.
|
|
@@ -20,9 +20,9 @@ export function itemPackJsonDirs(config?: object): string[];
|
|
|
20
20
|
* The passes that compiled nothing when they were expected to compile
|
|
21
21
|
* something — a build failure, not a quiet no-op.
|
|
22
22
|
*
|
|
23
|
-
* A pack
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* A pack ships blank whenever every note in a full tree was rejected — by a
|
|
24
|
+
* `selects` that claims nothing, or a `pack:` that routes everything elsewhere
|
|
25
|
+
* — and the build then exits 0 (#1502). The empty-tree guard in
|
|
26
26
|
* {@link generatePacksJson} cannot see that: the tree is full, it is the
|
|
27
27
|
* *output* that is empty.
|
|
28
28
|
*
|
|
@@ -274,14 +274,17 @@ export function collectContentDocs(contentBase: string): Array<{
|
|
|
274
274
|
* Expand the fenced `dataview` tables in one note's markdown, before wikilinks
|
|
275
275
|
* are resolved — so a generated cell may itself be a wikilink.
|
|
276
276
|
*
|
|
277
|
-
* A table searches only notes of the source note's own
|
|
278
|
-
*
|
|
277
|
+
* A table searches only notes of the source note's own package, so a SoHL page
|
|
278
|
+
* never tabulates setting-package content (and vice versa). Each candidate's
|
|
279
|
+
* package is **derived** rather than read out of its frontmatter: `package:` is
|
|
280
|
+
* optional, and comparing a declared value with an absent one would drop every
|
|
281
|
+
* unswept — or every swept — note from the table (#56).
|
|
279
282
|
*
|
|
280
283
|
* @param {string} body - The note's markdown body.
|
|
281
284
|
* @param {object} ctx
|
|
282
285
|
* @param {Array<object>} ctx.docs - From {@link collectContentDocs}.
|
|
283
286
|
* @param {string} ctx.name - The note, for the error message.
|
|
284
|
-
* @param {string} [ctx.pkg] - The source note's
|
|
287
|
+
* @param {string} [ctx.pkg] - The source note's package.
|
|
285
288
|
* @param {object} [ctx.fm] - The source note's frontmatter, which is what a
|
|
286
289
|
* query's `this` reads. Its entry in `docs` supplies the path as well.
|
|
287
290
|
* @param {number} [ctx.bodyLine] - 1-based file line of the body's first line,
|
package/types/engine/index.d.mts
CHANGED
|
@@ -5,6 +5,7 @@ export * as contentTree from "./content-tree.mjs";
|
|
|
5
5
|
export * as packConfig from "./pack-config.mjs";
|
|
6
6
|
export * as packRouter from "./pack-router.mjs";
|
|
7
7
|
export * as contentPackage from "./content-package.mjs";
|
|
8
|
+
export * as notePackage from "./note-package.mjs";
|
|
8
9
|
export * as contentSlug from "./content-slug.mjs";
|
|
9
10
|
export * as contentAddress from "./content-address.mjs";
|
|
10
11
|
export * as foreignManifests from "./foreign-manifests.mjs";
|
|
@@ -122,8 +122,8 @@ export const DEFAULT_MACRO_IMG: "icons/svg/dice-target.svg";
|
|
|
122
122
|
/**
|
|
123
123
|
* Macros pack compiler.
|
|
124
124
|
*
|
|
125
|
-
* Walks the content tree and compiles every `
|
|
126
|
-
*
|
|
125
|
+
* Walks the content tree and compiles every `type: macro` note into one Macro
|
|
126
|
+
* document. The same note's documentation is compiled by
|
|
127
127
|
* the journals pass; neither pass reads the other's output.
|
|
128
128
|
*/
|
|
129
129
|
export class Macros extends BasePackCompiler {
|
|
@@ -39,9 +39,12 @@ export function entriesForNote(fm: object, name: string, address: string, body:
|
|
|
39
39
|
*
|
|
40
40
|
* Drafts are excluded because the site does not publish them, and an entry for
|
|
41
41
|
* an unpublished page is exactly the dead link the manifest exists to prevent.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* Every note in the tree is this package's note, whether or not it says so:
|
|
44
|
+
* `package:` is optional and merely has to agree (#56). A note naming a
|
|
45
|
+
* different package **throws** rather than being skipped — this build is not
|
|
46
|
+
* authoritative for it, and skipping it silently is how a whole tree came to be
|
|
47
|
+
* filtered out of a manifest that then claimed the package published nothing.
|
|
45
48
|
*
|
|
46
49
|
* A note that has no address is **reported, not guessed** — the finding carries
|
|
47
50
|
* the file and the reason, so a caller can print it or fail on it. Inventing an
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package a note belongs to.
|
|
3
|
+
*
|
|
4
|
+
* Non-validating: the answer for a note that declares nothing, and for one that
|
|
5
|
+
* declares the configured package, is the same value. A note declaring some
|
|
6
|
+
* *other* package is answered literally here rather than corrected — the
|
|
7
|
+
* compile pass reports that, once, through {@link assertNotePackage}.
|
|
8
|
+
*
|
|
9
|
+
* @param {object|null|undefined} fm - Parsed frontmatter, or nothing when it
|
|
10
|
+
* could not be parsed.
|
|
11
|
+
* @param {string} [configured] - The package this build compiles. Defaults to
|
|
12
|
+
* the configured `contentPackage`; passed explicitly by callers that already
|
|
13
|
+
* carry it in a context object, so a caller's configuration drives every read.
|
|
14
|
+
* @returns {string} The package.
|
|
15
|
+
*/
|
|
16
|
+
export function notePackage(fm: object | null | undefined, configured?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* A note's frontmatter as a generated table searches it — its package present
|
|
19
|
+
* whether or not the note declares one.
|
|
20
|
+
*
|
|
21
|
+
* A `dataview` query resolves `package` out of frontmatter like any other
|
|
22
|
+
* field, so a collection note that scopes itself with `WHERE … and package =
|
|
23
|
+
* "sohl"` matches nothing once the field is deleted, and renders an **empty
|
|
24
|
+
* table** in silence. Deriving the value here keeps the two spellings
|
|
25
|
+
* equivalent, so a sweep that deletes the field is mechanical rather than a
|
|
26
|
+
* trap (#56) — and a query that never mentions `package` is unaffected either
|
|
27
|
+
* way.
|
|
28
|
+
*
|
|
29
|
+
* The declared value is left alone when there is one, so nothing about an
|
|
30
|
+
* unswept tree changes.
|
|
31
|
+
*
|
|
32
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
33
|
+
* @param {string} [configured] - The package this build compiles.
|
|
34
|
+
* @returns {object|null|undefined} The frontmatter itself when it declares a
|
|
35
|
+
* package, else a shallow copy carrying the derived one.
|
|
36
|
+
*/
|
|
37
|
+
export function searchableFrontmatter(fm: object | null | undefined, configured?: string): object | null | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The package a note belongs to, refusing one that names another package.
|
|
40
|
+
*
|
|
41
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
42
|
+
* @param {object} [options] - Options.
|
|
43
|
+
* @param {string} [options.file] - The note's path, named in the message. Omit
|
|
44
|
+
* it where the caller emits through a diagnostic, which puts the locator at
|
|
45
|
+
* the start of the line already — repeating it prints the path twice.
|
|
46
|
+
* @param {string} [options.configured] - The package this build compiles.
|
|
47
|
+
* Defaults to the configured `contentPackage`.
|
|
48
|
+
* @returns {string} The package, which is always `configured`.
|
|
49
|
+
* @throws {Error} When the note declares a different package.
|
|
50
|
+
*/
|
|
51
|
+
export function assertNotePackage(fm: object | null | undefined, { file, configured }?: {
|
|
52
|
+
file?: string | undefined;
|
|
53
|
+
configured?: string | undefined;
|
|
54
|
+
}): string;
|
|
@@ -46,8 +46,9 @@ export class PackRoutingError extends Error {
|
|
|
46
46
|
/**
|
|
47
47
|
* The frontmatter field a note declares its pack in.
|
|
48
48
|
*
|
|
49
|
-
* Deliberately close to `package:` and deliberately not the same
|
|
50
|
-
* `package:`
|
|
49
|
+
* Deliberately close to the retiring `package:` and deliberately not the same
|
|
50
|
+
* word: `package:` said which *distribution* owned a note — now the
|
|
51
|
+
* repository's `contentPackage` (#56) — while `pack:` says which *compendium*
|
|
51
52
|
* receives its document.
|
|
52
53
|
*/
|
|
53
54
|
export const PACK_FIELD: "pack";
|
|
@@ -16,7 +16,9 @@ export function walkSiteTree(dir: string, skip?: readonly string[]): string[];
|
|
|
16
16
|
* The content tree's pages, and what could not be addressed.
|
|
17
17
|
*
|
|
18
18
|
* @param {string} contentBase - Absolute path to the content tree.
|
|
19
|
-
* @param {object} ctx - `{ packages, skipDirectories, mount,
|
|
19
|
+
* @param {object} ctx - `{ packages, contentPackage, skipDirectories, mount,
|
|
20
|
+
* scheme }`. `contentPackage` is the package a note that declares none
|
|
21
|
+
* belongs to.
|
|
20
22
|
* @returns {{pages: object[], slugFindings: object[], fmLinkFindings: object[]}}
|
|
21
23
|
*/
|
|
22
24
|
export function collectContentPages(contentBase: string, ctx: object): {
|
package/types/sohl/actors.d.mts
CHANGED
|
@@ -25,6 +25,33 @@ export class Actors extends BasePackCompiler {
|
|
|
25
25
|
* entry plus one per `sohl.items` entry. `sohl.skills` is ignored.
|
|
26
26
|
*/
|
|
27
27
|
buildEmbeddedItems(itemsMap: any, actorId: any, fm: any, ctx: any): any[];
|
|
28
|
+
/**
|
|
29
|
+
* Bake each unopened skill's opening mastery level into the document (#46).
|
|
30
|
+
*
|
|
31
|
+
* A skill whose `masteryLevelBase` is still null once the note's frontmatter
|
|
32
|
+
* has been merged onto the catalogue entry is *not yet opened*, and the
|
|
33
|
+
* client fills it in on import — `Skill Base × initSkillMult`, in
|
|
34
|
+
* `SkillLogic.initialize`. Computing it here instead leaves the compiled
|
|
35
|
+
* pack self-describing: what a being's skills open at is visible in the
|
|
36
|
+
* document, reviewable in a diff, and testable without standing up Foundry.
|
|
37
|
+
*
|
|
38
|
+
* This runs last because the Skill Base formula reads the actor's
|
|
39
|
+
* attributes, so every attribute item has to exist first. It only ever
|
|
40
|
+
* fills nulls — a skill that states a `masteryLevelBase`, whether from the
|
|
41
|
+
* catalogue or the note, keeps it untouched.
|
|
42
|
+
*
|
|
43
|
+
* **The scores used are the ones just written.** `SkillLogic` resolves
|
|
44
|
+
* `attr.<code>` to an attribute's *effective* score, after active effects;
|
|
45
|
+
* all this pass has is the `scoreBase` it set from `sohl.attributes`. For a
|
|
46
|
+
* compiled being carrying no attribute-altering effects the two agree,
|
|
47
|
+
* which is every being in content today. One that did carry such an effect
|
|
48
|
+
* would bake a Skill Base its client then disagrees with — that is the
|
|
49
|
+
* limit of doing this at build time, and the point to revisit if it bites.
|
|
50
|
+
*
|
|
51
|
+
* @param {object[]} items - The actor's embedded items, attributes included.
|
|
52
|
+
* @param {string} ctx - Diagnostic context (the actor's label).
|
|
53
|
+
*/
|
|
54
|
+
openUnopenedSkills(items: object[], ctx: string): void;
|
|
28
55
|
buildBeing(itemsMap: any, fm: any, body: any): {
|
|
29
56
|
name: any;
|
|
30
57
|
type: string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HârnMaster Skill Base reduction, mirroring SoHL's `sb()` helper exactly.
|
|
3
|
+
*
|
|
4
|
+
* @param {...number} values - One or more attribute values.
|
|
5
|
+
* @returns {number} The reduced Skill Base.
|
|
6
|
+
* @throws {Error} If called with no arguments.
|
|
7
|
+
*/
|
|
8
|
+
export function sb(...values: number[]): number;
|
|
9
|
+
/**
|
|
10
|
+
* Evaluate a `skillBaseFormula` against an actor's attribute scores.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors `SkillLogic.computeSkillBase`: an absent or blank formula is Skill
|
|
13
|
+
* Base `0` (not an error — a skill may legitimately have none), and the result
|
|
14
|
+
* is clamped to `>= 0`.
|
|
15
|
+
*
|
|
16
|
+
* @param {string|null|undefined} formula - The expression source.
|
|
17
|
+
* @param {Record<string, number>} attrs - Attribute scores by shortcode.
|
|
18
|
+
* @returns {{ value: number, error?: string }} The Skill Base, or the reason it
|
|
19
|
+
* could not be computed. On error `value` is `0`, matching the client.
|
|
20
|
+
*/
|
|
21
|
+
export function evaluateSkillBase(formula: string | null | undefined, attrs?: Record<string, number>): {
|
|
22
|
+
value: number;
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The mastery level an unopened skill opens at, or `null` when it does not
|
|
27
|
+
* open at all.
|
|
28
|
+
*
|
|
29
|
+
* The client's rule (`SkillLogic.initialize`) is `Skill Base × initSkillMult`,
|
|
30
|
+
* applied only when `masteryLevelBase` is unset and the skill is on an actor.
|
|
31
|
+
* Two build-side refinements, neither of which changes what a client computes:
|
|
32
|
+
*
|
|
33
|
+
* - **A zero or absent `initSkillMult` stays `null`.** The multiplier is the
|
|
34
|
+
* switch for whether a skill opens at all, so writing the `0` the arithmetic
|
|
35
|
+
* yields would claim the skill opened at zero rather than that it never
|
|
36
|
+
* opened. `null` is what the field means by *not yet opened*, and the client
|
|
37
|
+
* arrives at the same place either way.
|
|
38
|
+
* - **A fractional product is an error, not a rounding.** `masteryLevelBase` is
|
|
39
|
+
* an integer field (`min: 0`), so a fractional value cannot be persisted
|
|
40
|
+
* honestly — where the client multiplies raw into a modifier and is free to
|
|
41
|
+
* carry the fraction, this is not. Reporting it follows
|
|
42
|
+
* `resolveSkillAptitudes`, which rejects a fractional modifier rather than
|
|
43
|
+
* rounding one.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} system - The merged skill `system` block.
|
|
46
|
+
* @param {Record<string, number>} attrs - Attribute scores by shortcode.
|
|
47
|
+
* @returns {{ value: number|null, error?: string }} The opening mastery level,
|
|
48
|
+
* `null` to leave the field unset, or the reason it could not be computed.
|
|
49
|
+
*/
|
|
50
|
+
export function openingMasteryLevel(system?: object, attrs?: Record<string, number>): {
|
|
51
|
+
value: number | null;
|
|
52
|
+
error?: string;
|
|
53
|
+
};
|