@heroiclands/package-build 19.0.0 → 20.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1100 -0
- package/CONTENT.md +264 -33
- package/README.md +43 -4
- package/bin/content-build.mjs +94 -3
- package/config.mjs +9 -1
- package/content-config.mjs +99 -19
- package/docs/content-format.md +394 -72
- package/e2e.mjs +297 -3
- package/engine/actor-compiler.mjs +197 -7
- package/engine/address-charset.mjs +23 -5
- package/engine/base-compiler.mjs +63 -2
- package/engine/bundles.mjs +9 -0
- package/engine/content-address.mjs +92 -1
- package/engine/content-charset.mjs +434 -0
- package/engine/content-format.mjs +102 -0
- package/engine/content-icons.mjs +388 -0
- package/engine/content-index.mjs +11 -8
- package/engine/content-links.mjs +37 -21
- package/engine/field-reference.mjs +57 -5
- package/engine/field-spec.mjs +214 -7
- package/engine/folder-notes.mjs +24 -1
- package/engine/foreign-catalog.mjs +112 -7
- package/engine/foundry-entries.mjs +14 -0
- package/engine/frontmatter-lint.mjs +377 -56
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +72 -12
- package/engine/helpers.mjs +96 -10
- package/engine/index.mjs +9 -0
- package/engine/item-compiler.mjs +37 -0
- package/engine/journals.mjs +21 -4
- package/engine/macros.mjs +8 -0
- package/engine/map-notes.mjs +7 -7
- package/engine/note-claims.mjs +208 -5
- package/engine/note-ids.mjs +25 -1
- package/engine/note-vocabulary.mjs +76 -9
- package/engine/pack-config.mjs +102 -12
- package/engine/pack-router.mjs +0 -0
- package/engine/prose-config.mjs +42 -0
- package/engine/prose-lint.mjs +126 -0
- package/engine/retired-fields.mjs +57 -16
- package/engine/runtime-only-fields.mjs +204 -0
- package/engine/scenes.mjs +12 -19
- package/engine/schema-check.mjs +23 -1
- package/engine/schema-extract.mjs +13 -0
- package/engine/site-index.mjs +17 -0
- package/engine/subtype-registry.mjs +30 -0
- package/engine/system-block.mjs +81 -3
- package/engine/web-wikilinks.mjs +33 -27
- package/engine/wikilink-syntax.mjs +7 -0
- package/engine/wikilinks.mjs +74 -16
- package/hm3/actors.mjs +63 -13
- package/package.json +2 -2
- package/sohl/actors.mjs +106 -7
- package/sohl/item-fields.mjs +203 -0
- package/sohl/note-schemas.mjs +6 -3
- package/types/config.d.mts +7 -0
- package/types/e2e.d.mts +130 -3
- package/types/engine/actor-compiler.d.mts +83 -3
- package/types/engine/address-charset.d.mts +22 -4
- package/types/engine/base-compiler.d.mts +54 -3
- package/types/engine/content-address.d.mts +64 -0
- package/types/engine/content-charset.d.mts +127 -0
- package/types/engine/content-format.d.mts +9 -0
- package/types/engine/content-icons.d.mts +151 -0
- package/types/engine/field-spec.d.mts +271 -3
- package/types/engine/folder-notes.d.mts +20 -0
- package/types/engine/foreign-catalog.d.mts +38 -2
- package/types/engine/foundry-entries.d.mts +6 -0
- package/types/engine/frontmatter-lint.d.mts +164 -30
- package/types/engine/frontmatter.d.mts +11 -11
- package/types/engine/generate.d.mts +27 -0
- package/types/engine/helpers.d.mts +45 -9
- package/types/engine/index.d.mts +3 -0
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/note-claims.d.mts +67 -0
- package/types/engine/note-ids.d.mts +14 -0
- package/types/engine/pack-config.d.mts +35 -0
- package/types/engine/prose-config.d.mts +41 -0
- package/types/engine/prose-lint.d.mts +36 -0
- package/types/engine/retired-fields.d.mts +29 -13
- package/types/engine/runtime-only-fields.d.mts +102 -0
- package/types/engine/schema-check.d.mts +10 -1
- package/types/engine/subtype-registry.d.mts +21 -0
- package/types/engine/system-block.d.mts +28 -2
- package/types/sohl/actors.d.mts +3 -3
|
@@ -1,3 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one system block accepts beyond the keys every block carries.
|
|
3
|
+
*
|
|
4
|
+
* @typedef {object} SystemBlockSpec
|
|
5
|
+
* @property {readonly string[]} [known] - Keys stated outright.
|
|
6
|
+
* @property {boolean} [fieldVocabulary] - Whether the note type's declared field
|
|
7
|
+
* names, as the caller's `schemas` state them, are keys of this block.
|
|
8
|
+
* @property {Readonly<Record<string, readonly object[]>>} [fields] - Type → this
|
|
9
|
+
* system's own declared fields. A type it does not name is a type this system
|
|
10
|
+
* says nothing about, and its block is left unchecked on such a note rather
|
|
11
|
+
* than reported wholesale.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Every system a configuration says its tree carries (#58).
|
|
15
|
+
*
|
|
16
|
+
* **Which systems a package ships for is already declared**, in three places
|
|
17
|
+
* that answer different questions, so this reads all three rather than asking a
|
|
18
|
+
* new one:
|
|
19
|
+
*
|
|
20
|
+
* - `systems:` (#48) declares them without requiring one, which is how a
|
|
21
|
+
* package ships for several;
|
|
22
|
+
* - a **pack's** `system:` is the same statement made per pack, and it is the
|
|
23
|
+
* one some trees make: `harn-ensemble` declares an `actors-sohl` and an
|
|
24
|
+
* `actors-hm3` and nothing else about either system. It is already
|
|
25
|
+
* authoritative elsewhere — `eligibleFor` fails a note for want of the block
|
|
26
|
+
* a pack's `system:` names — so a lint that did not read it would refuse a
|
|
27
|
+
* note at compile for a block it never checked;
|
|
28
|
+
* - `stats.systemId` is the package-wide answer where there is one, and it has
|
|
29
|
+
* already absorbed every way of spelling that: a system package is its own
|
|
30
|
+
* system, and a module takes `requiresSystem`, its lone `systems:` entry, or
|
|
31
|
+
* its lone system relationship.
|
|
32
|
+
*
|
|
33
|
+
* A package naming a system in none of them is system-agnostic on purpose — its
|
|
34
|
+
* packs are core document types carrying no system data — so it carries no
|
|
35
|
+
* system block and naming one would invent it.
|
|
36
|
+
*
|
|
37
|
+
* @param {object} [config] - A resolved configuration from `defineConfig`.
|
|
38
|
+
* @returns {string[]} The system ids, deduplicated, in declared order.
|
|
39
|
+
*/
|
|
40
|
+
export function declaredSystems(config?: object): string[];
|
|
41
|
+
/**
|
|
42
|
+
* The system blocks a configuration says its tree carries, and what each
|
|
43
|
+
* accepts (#58).
|
|
44
|
+
*
|
|
45
|
+
* The lint checks the blocks its caller names, and for as long as there was one
|
|
46
|
+
* system the only caller named none — so every tree took the `sohl:` of
|
|
47
|
+
* {@link DEFAULT_SYSTEM_BLOCKS}, a constant, in a module whose whole discipline
|
|
48
|
+
* is that it states no vocabulary of its own. That is wrong in both directions
|
|
49
|
+
* the moment a second system exists, and the second direction is the worse:
|
|
50
|
+
*
|
|
51
|
+
* - a package shipping for `hm3` had its `hm3:` block **never looked at**, so
|
|
52
|
+
* every key in it was discarded at compile without a word — the silent-drop
|
|
53
|
+
* family this check exists to close;
|
|
54
|
+
* - and the block that *was* checked was named after a system that package does
|
|
55
|
+
* not ship for, so the one finding it could make was about nothing.
|
|
56
|
+
*
|
|
57
|
+
* **A block's vocabulary has two sources, and a system may have both.**
|
|
58
|
+
*
|
|
59
|
+
* - The **note schemas** the caller hands in as `schemas`. Those belong to one
|
|
60
|
+
* system — the CLI imports `sohl/note-schemas.mjs` — and `schemaSystem` is the
|
|
61
|
+
* caller naming which, because only the caller knows. It is the only source
|
|
62
|
+
* that reaches a type no item registry declares, which is to say `being`: the
|
|
63
|
+
* 2,512 notes `harn-ensemble` is made of, and the reason this is not an
|
|
64
|
+
* optional refinement.
|
|
65
|
+
* - The system's **own registry**, `itemFieldsBySystem`, keyed by system and
|
|
66
|
+
* until now read by nothing. This is what a *second* system's block is held
|
|
67
|
+
* to, since the note schemas describe its neighbour.
|
|
68
|
+
*
|
|
69
|
+
* A system with neither is left out: nothing can state what its block may
|
|
70
|
+
* carry, and holding it to an empty vocabulary would report every key in a
|
|
71
|
+
* correct tree. **That is a check that does not run**, which is
|
|
72
|
+
* indistinguishable from one that passed, so the caller says it out loud —
|
|
73
|
+
* {@link declaredSystems} is the other half of that comparison. `harn-ensemble`
|
|
74
|
+
* is the tree it names: two systems, and an `itemBuilders` registry for
|
|
75
|
+
* neither, so its `hm3:` block is unchecked until it declares one.
|
|
76
|
+
*
|
|
77
|
+
* An earlier draft of this took the note schemas for a system's vocabulary only
|
|
78
|
+
* where the package declared **one** system, on the reasoning that with several
|
|
79
|
+
* there is nothing to say which one they describe. There is: the caller, which
|
|
80
|
+
* chose them. The guess cost `harn-ensemble` its whole `sohl:` check — two
|
|
81
|
+
* systems declared, so the fallback never fired — which is the coverage this
|
|
82
|
+
* change exists to widen rather than narrow.
|
|
83
|
+
*
|
|
84
|
+
* @param {object} [config] - A resolved configuration from `defineConfig`.
|
|
85
|
+
* @param {object} [options] - Options.
|
|
86
|
+
* @param {string} [options.schemaSystem] - The system whose vocabulary the
|
|
87
|
+
* caller's `schemas` state. There are two systems, not an open set, so this is
|
|
88
|
+
* one word from the caller rather than a mechanism.
|
|
89
|
+
* @returns {Readonly<Record<string, SystemBlockSpec>>} The blocks to check, in
|
|
90
|
+
* declared order. A system nothing states the vocabulary of is absent.
|
|
91
|
+
*/
|
|
92
|
+
export function systemBlocksFor(config?: object, { schemaSystem }?: {
|
|
93
|
+
schemaSystem?: string | undefined;
|
|
94
|
+
}): Readonly<Record<string, SystemBlockSpec>>;
|
|
1
95
|
/**
|
|
2
96
|
* Whether a value satisfies a declared {@link FieldSpec.kind}.
|
|
3
97
|
*
|
|
@@ -26,23 +120,32 @@ export function matchesKind(value: unknown, kind: string): boolean;
|
|
|
26
120
|
* note against whatever its type declares and knows no type names of its
|
|
27
121
|
* own. Its absence skips the `data:` and `subType` checks rather than
|
|
28
122
|
* reporting every key as unknown.
|
|
29
|
-
* @param {
|
|
30
|
-
*
|
|
31
|
-
*
|
|
123
|
+
* @param {(type: string) => {document: string|null, art: readonly string[]}|null} [opts.emittedArt]
|
|
124
|
+
* What art a note of one type reaches its document through — the passes' own
|
|
125
|
+
* declaration, asked through `engine/generate.mjs`'s `emittedArtFor`.
|
|
126
|
+
* Supplied by the caller like `schemas`, so this module states no list of
|
|
127
|
+
* iconless types of its own; absent it, an inert `img:` goes unreported
|
|
128
|
+
* rather than every note's being (#349).
|
|
129
|
+
* @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
|
|
130
|
+
* The system blocks to check, and what each accepts. Supplied by the caller
|
|
131
|
+
* for the same reason `schemas` is — a build derives them from its
|
|
132
|
+
* configuration through {@link systemBlocksFor}, and this module states no
|
|
133
|
+
* system name of its own. See {@link DEFAULT_SYSTEM_BLOCKS} for the fallback.
|
|
32
134
|
* @param {readonly string[]} [opts.packs] - The pack names this package
|
|
33
135
|
* declares, for a `data:` field whose map is keyed by pack. Supplied by the
|
|
34
136
|
* caller like `schemas` and `vocabulary`, and absent it no claim is made
|
|
35
137
|
* about those keys.
|
|
36
138
|
* @returns {object[]} Findings, each with a locator where one is obtainable.
|
|
37
139
|
*/
|
|
38
|
-
export function lintNote(note: object, { schemas, index, vocabulary, packs, systems }: {
|
|
140
|
+
export function lintNote(note: object, { schemas, index, vocabulary, packs, emittedArt, systems }: {
|
|
39
141
|
schemas: Record<string, readonly object[]>;
|
|
40
142
|
index?: object | undefined;
|
|
41
143
|
vocabulary?: Record<string, object> | undefined;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
144
|
+
emittedArt?: ((type: string) => {
|
|
145
|
+
document: string | null;
|
|
146
|
+
art: readonly string[];
|
|
147
|
+
} | null) | undefined;
|
|
148
|
+
systems?: Readonly<Record<string, SystemBlockSpec>> | undefined;
|
|
46
149
|
packs?: readonly string[] | undefined;
|
|
47
150
|
}): object[];
|
|
48
151
|
/**
|
|
@@ -54,22 +157,26 @@ export function lintNote(note: object, { schemas, index, vocabulary, packs, syst
|
|
|
54
157
|
* @param {Record<string, object>} [opts.vocabulary] - Type → the closed regions
|
|
55
158
|
* it declares (#128); see {@link lintNote}.
|
|
56
159
|
* @param {boolean} [opts.references=true] - Whether to check references.
|
|
57
|
-
* @param {Readonly<Record<string,
|
|
58
|
-
* The system blocks to check
|
|
160
|
+
* @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
|
|
161
|
+
* The system blocks to check; see {@link lintNote} and
|
|
162
|
+
* {@link systemBlocksFor}.
|
|
59
163
|
* @param {readonly string[]} [opts.packs] - The declared pack names; see
|
|
60
164
|
* {@link lintNote}.
|
|
165
|
+
* @param {(type: string) => {document: string|null, art: readonly string[]}|null} [opts.emittedArt]
|
|
166
|
+
* What art a type reaches its document through; see {@link lintNote}.
|
|
61
167
|
* @returns {{findings: object[], notes: number}} The findings, and how many
|
|
62
168
|
* notes were inspected.
|
|
63
169
|
*/
|
|
64
|
-
export function lintFrontmatter(index: object, { schemas, vocabulary, packs, references, systems }: {
|
|
170
|
+
export function lintFrontmatter(index: object, { schemas, vocabulary, packs, emittedArt, references, systems }: {
|
|
65
171
|
schemas: Record<string, readonly object[]>;
|
|
66
172
|
vocabulary?: Record<string, object> | undefined;
|
|
67
173
|
references?: boolean | undefined;
|
|
68
|
-
systems?: Readonly<Record<string,
|
|
69
|
-
known?: readonly string[];
|
|
70
|
-
fieldVocabulary?: boolean;
|
|
71
|
-
}>> | undefined;
|
|
174
|
+
systems?: Readonly<Record<string, SystemBlockSpec>> | undefined;
|
|
72
175
|
packs?: readonly string[] | undefined;
|
|
176
|
+
emittedArt?: ((type: string) => {
|
|
177
|
+
document: string | null;
|
|
178
|
+
art: readonly string[];
|
|
179
|
+
} | null) | undefined;
|
|
73
180
|
}): {
|
|
74
181
|
findings: object[];
|
|
75
182
|
notes: number;
|
|
@@ -107,21 +214,48 @@ export const UNIVERSAL_KEYS: ReadonlySet<string>;
|
|
|
107
214
|
* The system blocks a build checks, and what each accepts beyond the shared
|
|
108
215
|
* vocabulary.
|
|
109
216
|
*
|
|
110
|
-
* One entry, because one system is what every existing tree declares — and
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* inventing a rule for it would report a correct tree red.
|
|
217
|
+
* One entry, because one system is what every existing tree declares — and it
|
|
218
|
+
* is a *fallback*, not the rule. {@link systemBlocksFor} derives the map from
|
|
219
|
+
* the configuration, which is what makes the block a package actually ships for
|
|
220
|
+
* the block that gets checked; this is what a caller holding no configuration
|
|
221
|
+
* gets, which in practice is a unit test.
|
|
116
222
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* `itemBuilders` registry that this system declares, and a second system's
|
|
120
|
-
* notes write a second system's fields.
|
|
223
|
+
* A block nothing declares is not checked, because nothing can say what it may
|
|
224
|
+
* carry, and inventing a rule for it would report a correct tree red.
|
|
121
225
|
*
|
|
122
|
-
*
|
|
226
|
+
* Three ways a block may state its vocabulary, and a spec declares at most one:
|
|
227
|
+
*
|
|
228
|
+
* - `known` — an explicit list of keys, for a caller stating them outright.
|
|
229
|
+
* - `fieldVocabulary` — the note type's own declared field names, as the
|
|
230
|
+
* caller's `schemas` state them, are keys of this block. That holds for the
|
|
231
|
+
* **one** system a single-registry tree ships for, where `schemas` *is* that
|
|
232
|
+
* system's vocabulary, and in general holds for no other.
|
|
233
|
+
* - `fields` — type → that system's own declared fields, from the registry the
|
|
234
|
+
* system declares. What a second system's block is checked against, because a
|
|
235
|
+
* second system's notes write a second system's fields and the note-type
|
|
236
|
+
* schemas describe somebody else's.
|
|
237
|
+
*
|
|
238
|
+
* @type {Readonly<Record<string, SystemBlockSpec>>}
|
|
123
239
|
*/
|
|
124
|
-
export const DEFAULT_SYSTEM_BLOCKS: Readonly<Record<string,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
240
|
+
export const DEFAULT_SYSTEM_BLOCKS: Readonly<Record<string, SystemBlockSpec>>;
|
|
241
|
+
/**
|
|
242
|
+
* What one system block accepts beyond the keys every block carries.
|
|
243
|
+
*/
|
|
244
|
+
export type SystemBlockSpec = {
|
|
245
|
+
/**
|
|
246
|
+
* - Keys stated outright.
|
|
247
|
+
*/
|
|
248
|
+
known?: readonly string[] | undefined;
|
|
249
|
+
/**
|
|
250
|
+
* - Whether the note type's declared field
|
|
251
|
+
* names, as the caller's `schemas` state them, are keys of this block.
|
|
252
|
+
*/
|
|
253
|
+
fieldVocabulary?: boolean | undefined;
|
|
254
|
+
/**
|
|
255
|
+
* - Type → this
|
|
256
|
+
* system's own declared fields. A type it does not name is a type this system
|
|
257
|
+
* says nothing about, and its block is left unchecked on such a note rather
|
|
258
|
+
* than reported wholesale.
|
|
259
|
+
*/
|
|
260
|
+
fields?: Readonly<Record<string, readonly object[]>> | undefined;
|
|
261
|
+
};
|
|
@@ -132,13 +132,15 @@ export function parseValueDesc(raw: any): {
|
|
|
132
132
|
maxValue: number;
|
|
133
133
|
}[];
|
|
134
134
|
/**
|
|
135
|
-
* The compendium folder a note names
|
|
135
|
+
* The compendium folder a note names.
|
|
136
136
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
137
|
+
* **There is one spelling.** `packFolder:` is a folder note's **address**
|
|
138
|
+
* (`folder-poisonsandtoxins`), resolved through the address index the whole
|
|
139
|
+
* build shares. The `folder:` Foundry-id spelling this function once read
|
|
140
|
+
* beside it, and the per-pack `*-folders.yaml` that id was resolved against,
|
|
141
|
+
* are retired together (#260) — a note declaring `folder:` is refused by
|
|
142
|
+
* {@link module:engine/folder-notes.assertNoDeclaredFolder} rather than
|
|
143
|
+
* reaching here, so there is no second source for a value to come from.
|
|
142
144
|
*
|
|
143
145
|
* **`packFolder` was a path for one release** (`Possessions/Misc_Gear/Cooking`)
|
|
144
146
|
* and is an address now (#255). A path encoded the hierarchy *in the value*, so
|
|
@@ -149,13 +151,11 @@ export function parseValueDesc(raw: any): {
|
|
|
149
151
|
* authors to migrate, which is the whole reason the change was cheap enough to
|
|
150
152
|
* make.
|
|
151
153
|
*
|
|
152
|
-
* `packFolder` wins where both are present. Nothing about `folder` changes: a
|
|
153
|
-
* note that names one is read, resolved and emitted exactly as before, until
|
|
154
|
-
* #260 retires it.
|
|
155
|
-
*
|
|
156
154
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
157
155
|
* @returns {{value: string|null, isAddress: boolean}} The authored value, and
|
|
158
|
-
* whether it is a folder note's address.
|
|
156
|
+
* whether it is a folder note's address. `isAddress` is always `true` and is
|
|
157
|
+
* kept so a caller reads the same shape it always did; it distinguished the
|
|
158
|
+
* two spellings, and there is only one left to be.
|
|
159
159
|
*/
|
|
160
160
|
export function folderField(fm: object | null | undefined): {
|
|
161
161
|
value: string | null;
|
|
@@ -8,6 +8,33 @@
|
|
|
8
8
|
* rather than defaulting past.
|
|
9
9
|
*/
|
|
10
10
|
export function compilerFor(docType: string, system?: string | null): Function | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The art fields a note of one content type reaches its document through, and
|
|
13
|
+
* the document it reaches (#349).
|
|
14
|
+
*
|
|
15
|
+
* **Derived, never listed.** A note's type routes to a document type
|
|
16
|
+
* ({@link packForType}), a document type routes to the pass that compiles it
|
|
17
|
+
* ({@link compilerFor}), and the pass declares which art it emits
|
|
18
|
+
* ({@link BasePackCompiler.emitsArt}). So the answer is assembled from the same
|
|
19
|
+
* three statements the compile itself follows, and a pass that starts or stops
|
|
20
|
+
* emitting art changes this by changing its own declaration. A second table of
|
|
21
|
+
* "types with no image" would be a table free to drift from what is emitted,
|
|
22
|
+
* which is the defect this exists to report rather than to reproduce.
|
|
23
|
+
*
|
|
24
|
+
* **The union across systems**, because a note is compiled by whichever pack
|
|
25
|
+
* claims it: a tree feeding both SoHL and HM3 has two Actor passes, and a field
|
|
26
|
+
* either of them emits is live for the note. Only a field *no* pass emits is
|
|
27
|
+
* inert, and that is the finding this supports.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} type - The note's content type.
|
|
30
|
+
* @returns {{document: string|null, art: readonly string[]}|null} What the type
|
|
31
|
+
* compiles into and the art it carries there, or `null` where no claim can be
|
|
32
|
+
* made — a retired type, which is reported as retired instead.
|
|
33
|
+
*/
|
|
34
|
+
export function emittedArtFor(type: string): {
|
|
35
|
+
document: string | null;
|
|
36
|
+
art: readonly string[];
|
|
37
|
+
} | null;
|
|
11
38
|
/**
|
|
12
39
|
* The generated JSON of **every** configured Item pack — what the actors pass
|
|
13
40
|
* reads its predefined items from.
|
|
@@ -169,19 +169,47 @@ export function systemTemplatePriority(fm: object, label: string): number | null
|
|
|
169
169
|
* alphanumeric runs replaced by underscores.
|
|
170
170
|
*/
|
|
171
171
|
export function makeFilename(name: any, id: any): string;
|
|
172
|
-
/**
|
|
173
|
-
* Standardize a name into a slug: lowercase, apostrophes removed,
|
|
174
|
-
* non-alphanumerics collapsed to single hyphens.
|
|
175
|
-
*/
|
|
176
172
|
/**
|
|
177
173
|
* Translate a content-relative image path into its Foundry-relative form.
|
|
178
174
|
*
|
|
179
175
|
* Content frontmatter (`img` / `portrait`) authors a single path that has to
|
|
180
|
-
* work for Foundry, the knowledgebase, and the website.
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
176
|
+
* work for Foundry, the knowledgebase, and the website. **Its first segment
|
|
177
|
+
* says which package owns the file** (#331), and there are exactly three
|
|
178
|
+
* answers:
|
|
179
|
+
*
|
|
180
|
+
* | Authored path starts with | Owner | Emitted |
|
|
181
|
+
* | ------------------------- | --------------------- | -------------------- |
|
|
182
|
+
* | `systems/` | a separate **system** | unchanged |
|
|
183
|
+
* | `modules/` | a separate **module** | unchanged |
|
|
184
|
+
* | anything else | **this package** | `<assetRoot>/<path>` |
|
|
185
|
+
*
|
|
186
|
+
* So `icons/relic.svg` compiles to `systems/sohl/assets/icons/relic.svg` here
|
|
187
|
+
* and to `modules/sohl-thalorna/assets/icons/relic.svg` in a module — the asset
|
|
188
|
+
* root is derived from the configuration, and is the one place `systems/sohl`
|
|
189
|
+
* is ever spelled (#1508). An authored
|
|
190
|
+
* `systems/sohl/assets/icons/noun/shield.svg` is left exactly as written,
|
|
191
|
+
* whichever package is compiling it.
|
|
192
|
+
*
|
|
193
|
+
* **This is a rule about ownership, not an allowlist of directories.** It used
|
|
194
|
+
* to prefix `icons/…` and `images/…` and pass everything else through — the
|
|
195
|
+
* same answer for every path any tree authors today, and the wrong one for the
|
|
196
|
+
* next directory a package ships. `sohl-kethira-basic` keeps art under
|
|
197
|
+
* `assets/artwork/`, so an authored `artwork/deity.webp` would have shipped
|
|
198
|
+
* unprefixed: a 404 in Foundry, reported by nothing. That a package owns its
|
|
199
|
+
* own tree is the fact; the directory names inside it are that package's
|
|
200
|
+
* business (#331).
|
|
201
|
+
*
|
|
202
|
+
* **Off-install addresses pass through too**, which is the same rule rather
|
|
203
|
+
* than a fourth: a URL, a `data:` URI, or a `/`-rooted path names something no
|
|
204
|
+
* package owns. See {@link addressesAnotherPackage}.
|
|
205
|
+
*
|
|
206
|
+
* **`banner:` does not follow this rule, deliberately (#331).** It is not an
|
|
207
|
+
* asset path inside a Foundry install at all: it reaches no compiled document,
|
|
208
|
+
* and its only consumer is the Hugo theme, which prefixes a relative value with
|
|
209
|
+
* `images/` and joins it onto `params.cdnBaseURL`. The two fields look alike
|
|
210
|
+
* and address different places — `img:` a file Foundry serves, `banner:` a file
|
|
211
|
+
* the CDN serves — so they are documented apart rather than reconciled into one
|
|
212
|
+
* rule that would be true of neither.
|
|
185
213
|
*
|
|
186
214
|
* **Two empties, and they mean opposite things (#218).** `null` — or an absent
|
|
187
215
|
* key, which reaches here as `undefined` — means _unset_: the note names no art
|
|
@@ -434,6 +462,14 @@ export function expandNoteTables(body: string, { docs, name, fm, bodyLine, sqlTa
|
|
|
434
462
|
* underscores.
|
|
435
463
|
*/
|
|
436
464
|
export function folderFilename(name: any, id: any): string;
|
|
465
|
+
/**
|
|
466
|
+
* The markdown renderer every surface shares.
|
|
467
|
+
*
|
|
468
|
+
* `html: true` is long-standing and load-bearing — notes carry raw blocks — and
|
|
469
|
+
* it is also why {@link module:engine/content-icons} exists rather than an
|
|
470
|
+
* instruction to write `<i class="fa-solid …">` by hand: that would render on
|
|
471
|
+
* the two HTML surfaces and be silently dropped by the third (#378).
|
|
472
|
+
*/
|
|
437
473
|
export const md: import("markdown-it").MarkdownIt;
|
|
438
474
|
export { slugify } from "./content-slug.mjs";
|
|
439
475
|
export { makeId } from "./ids.mjs";
|
package/types/engine/index.d.mts
CHANGED
|
@@ -10,6 +10,7 @@ export * as folderNotes from "./folder-notes.mjs";
|
|
|
10
10
|
export * as contentPackage from "./content-package.mjs";
|
|
11
11
|
export * as notePackage from "./note-package.mjs";
|
|
12
12
|
export * as retiredFields from "./retired-fields.mjs";
|
|
13
|
+
export * as runtimeOnlyFields from "./runtime-only-fields.mjs";
|
|
13
14
|
export * as homepage from "./homepage.mjs";
|
|
14
15
|
export * as noteSchemas from "./note-schemas.mjs";
|
|
15
16
|
export * as noteVocabulary from "./note-vocabulary.mjs";
|
|
@@ -24,6 +25,8 @@ export * as foundryEntries from "./foundry-entries.mjs";
|
|
|
24
25
|
export * as contentIndex from "./content-index.mjs";
|
|
25
26
|
export * as siteBuild from "./site-build.mjs";
|
|
26
27
|
export * as contentLint from "./content-lint.mjs";
|
|
28
|
+
export * as contentCharset from "./content-charset.mjs";
|
|
29
|
+
export * as contentIcons from "./content-icons.mjs";
|
|
27
30
|
export * as contentLinks from "./content-links.mjs";
|
|
28
31
|
export * as webWikilinks from "./web-wikilinks.mjs";
|
|
29
32
|
export * as contentTables from "./content-tables.mjs";
|
|
@@ -158,8 +158,8 @@ export function buildScene(fm: object, ctx: object): object;
|
|
|
158
158
|
* @param {string} sceneId - The owning scene's `_id`.
|
|
159
159
|
* @param {string} [img] - The background art, already resolved from the note.
|
|
160
160
|
* Passed by {@link buildScene}, which reads it from the note rather than from
|
|
161
|
-
* the block; defaults to
|
|
162
|
-
*
|
|
161
|
+
* the block; defaults to the block's own `img`, so a direct two-argument call
|
|
162
|
+
* still works.
|
|
163
163
|
* @returns {object} The Level document, keyed for the pack.
|
|
164
164
|
*/
|
|
165
165
|
export function buildLevel(sohl: object, sceneId: string, img?: string): object;
|
|
@@ -15,6 +15,59 @@
|
|
|
15
15
|
* @returns {ReadonlySet<string>} The note types such a pass would claim.
|
|
16
16
|
*/
|
|
17
17
|
export function noteTypesClaimedBy(docType: string, sources?: ClaimSources): ReadonlySet<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Every document class a note of one type compiles into (#152).
|
|
20
|
+
*
|
|
21
|
+
* **A note produces more than one document, and that is the ordinary case.** An
|
|
22
|
+
* item note compiles an Item *and* the JournalEntry its prose becomes; a map
|
|
23
|
+
* note a Scene and a JournalEntry; since #337 an actor note an Actor and a
|
|
24
|
+
* JournalEntry too. {@link claimedNoteTypes} unions over the configured packs
|
|
25
|
+
* and so answers "is this note compiled *at all*", which is #146's question and
|
|
26
|
+
* cannot see a note that compiles one of its two documents and loses the other.
|
|
27
|
+
*
|
|
28
|
+
* Asked of the **claim table** rather than of a list of its own, so the set of
|
|
29
|
+
* documents a type produces and the set of passes that claim it are one
|
|
30
|
+
* statement. A pass that starts claiming a type starts producing its document
|
|
31
|
+
* here, with nothing to remember.
|
|
32
|
+
*
|
|
33
|
+
* **Union across systems, never per system.** A type one system maps and
|
|
34
|
+
* another does not appears once, because the `Item` and `Actor` rows already
|
|
35
|
+
* fold the maps together — so this cannot report a document class a system
|
|
36
|
+
* deliberately declines to produce, which is the silence #79 requires.
|
|
37
|
+
*
|
|
38
|
+
* ## The JournalEntry row is the one that is per *note*
|
|
39
|
+
*
|
|
40
|
+
* Every other row is a property of the type: a `macro` note produces a Macro, a
|
|
41
|
+
* map note a Scene, whatever either says. Documentation is not. `Journals`
|
|
42
|
+
* declines a doc-carrying note whose body is empty — *"an item with no prose
|
|
43
|
+
* gets no doc, and the items pass leaves its description empty rather than
|
|
44
|
+
* pointing at nothing"* — so whether an item note produces a JournalEntry is
|
|
45
|
+
* decided by the note, not by its type.
|
|
46
|
+
*
|
|
47
|
+
* That distinction is the whole difference between a useful finding and a
|
|
48
|
+
* useless one. `sohl-kethira-basic` declares no JournalEntry pack and ships 393
|
|
49
|
+
* notes whose descriptions are *deliberately* empty, under the Fan Material
|
|
50
|
+
* Guidelines its configuration explains at length. A type-level answer would
|
|
51
|
+
* report every one of them for losing a document none of them produces. Asking
|
|
52
|
+
* per note, it reports none, and still reports `harn-ensemble`'s 2,517 beings,
|
|
53
|
+
* whose `{#appearance}` and `{#dossier}` prose is real and is lost.
|
|
54
|
+
*
|
|
55
|
+
* `hasProse` is therefore how the caller answers that, and it is a **thunk** so
|
|
56
|
+
* that the file is read only where the answer could change the outcome. Omitted,
|
|
57
|
+
* the answer is the type's full potential — every document such a note *could*
|
|
58
|
+
* produce — which is what a caller asking about a type rather than a note wants.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} type - The note's declared `type`, current spelling.
|
|
61
|
+
* @param {ClaimSources} [sources] - What to answer from.
|
|
62
|
+
* @param {object} [opts] - Options.
|
|
63
|
+
* @param {(() => boolean)|boolean} [opts.hasProse] - Whether *this note* carries
|
|
64
|
+
* a body. Omitted, the type's potential is reported.
|
|
65
|
+
* @returns {string[]} The document classes, in {@link CLAIMABLE_DOCUMENT_TYPES}
|
|
66
|
+
* order. Empty for a type nothing compiles.
|
|
67
|
+
*/
|
|
68
|
+
export function documentClassesFor(type: string, sources?: ClaimSources, { hasProse }?: {
|
|
69
|
+
hasProse?: boolean | (() => boolean) | undefined;
|
|
70
|
+
}): string[];
|
|
18
71
|
/**
|
|
19
72
|
* Every note type some pack in a configuration would compile.
|
|
20
73
|
*
|
|
@@ -133,6 +186,20 @@ export const UNIMPLEMENTED_TYPES: ReadonlySet<string>;
|
|
|
133
186
|
*/
|
|
134
187
|
export const DERIVED_PACKED_TYPES: ReadonlySet<string>;
|
|
135
188
|
export { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
|
|
189
|
+
/**
|
|
190
|
+
* Every Foundry document class {@link noteTypesClaimedBy} answers for.
|
|
191
|
+
*
|
|
192
|
+
* The switch above, read the other way round. It is written out rather than
|
|
193
|
+
* derived because a `switch` cannot be enumerated — and
|
|
194
|
+
* `tests/unclaimed-note-types.test.ts` holds the two together by checking that
|
|
195
|
+
* no class outside this list claims anything, so a row added there and not here
|
|
196
|
+
* fails rather than going quiet.
|
|
197
|
+
*
|
|
198
|
+
* Order is the order a reader meets them in a message, not a precedence.
|
|
199
|
+
*
|
|
200
|
+
* @type {readonly string[]}
|
|
201
|
+
*/
|
|
202
|
+
export const CLAIMABLE_DOCUMENT_TYPES: readonly string[];
|
|
136
203
|
/**
|
|
137
204
|
* What a claim question is asked against.
|
|
138
205
|
*
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The document id a note compiles under: its pin, or its address.
|
|
3
3
|
*
|
|
4
|
+
* **One type hashes its address differently, and that is not an exception to
|
|
5
|
+
* the rule but an application of it.** A `Folder` is a document of its own
|
|
6
|
+
* class, and its id is hashed under the `folder` namespace so that a folder and
|
|
7
|
+
* an item sharing a shortcode cannot derive one id — a collision Foundry would
|
|
8
|
+
* not report, since it keys folders and documents in separate collections
|
|
9
|
+
* (#258). So the answer for a folder comes from
|
|
10
|
+
* {@link module:engine/folder-notes.folderDocId}, the pass that emits those
|
|
11
|
+
* documents, rather than from a second derivation here.
|
|
12
|
+
*
|
|
13
|
+
* That this function ever answered differently was invisible from inside a
|
|
14
|
+
* build — no pass reads a folder's id from here — and surfaced only in the
|
|
15
|
+
* content index, which is read from outside and had no way to be checked
|
|
16
|
+
* against what shipped (#310).
|
|
17
|
+
*
|
|
4
18
|
* Returns `undefined` for a file with **no address** — no `type`, or no
|
|
5
19
|
* `shortcode`. Such a file is not an addressable note, so it has no document
|
|
6
20
|
* and inventing an id for one would file it under nothing. Every caller already
|
|
@@ -14,6 +14,41 @@
|
|
|
14
14
|
* {@link CONFIG_FILENAMES}.
|
|
15
15
|
*/
|
|
16
16
|
export function findConfigFile(from: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Which configuration file a build launched here should read, and what each
|
|
19
|
+
* walk found.
|
|
20
|
+
*
|
|
21
|
+
* Kept separate from {@link loadPackConfig} because the *choice* is worth being
|
|
22
|
+
* able to ask about without loading anything: the two walks disagreeing is the
|
|
23
|
+
* observable form of #364, and a caller that wants to report it — or a test
|
|
24
|
+
* that wants to describe it — should not have to reproduce the resolution and
|
|
25
|
+
* risk disagreeing with the loader about it. It performs I/O, and is named for
|
|
26
|
+
* it, like the {@link findConfigFile} it calls twice.
|
|
27
|
+
*
|
|
28
|
+
* `PACKAGE_BUILD_CONFIG` is deliberately not consulted here. An explicit name
|
|
29
|
+
* is not a search result: {@link loadPackConfig} short-circuits on it before it
|
|
30
|
+
* ever asks, so there is no walk to report and nothing to disagree with.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} [from] - Where to walk up from; both default to the real
|
|
33
|
+
* thing, and are parameters only so a caller can describe a tree it is not
|
|
34
|
+
* standing in.
|
|
35
|
+
* @param {string} [from.cwd] - The directory the build was launched in.
|
|
36
|
+
* @param {string} [from.moduleDir] - The directory this module sits in.
|
|
37
|
+
* @returns {{path: string|undefined, fromCwd: string|undefined, fromModule: string|undefined}}
|
|
38
|
+
* The file to read, and each walk's own answer — the same file in an ordinary
|
|
39
|
+
* build, different ones in a worktree resolving the toolchain out of its
|
|
40
|
+
* parent checkout.
|
|
41
|
+
* @throws {Error} As {@link findConfigFile}, when one directory holds more than
|
|
42
|
+
* one configuration.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveConfigFile({ cwd, moduleDir }?: {
|
|
45
|
+
cwd?: string | undefined;
|
|
46
|
+
moduleDir?: string | undefined;
|
|
47
|
+
}): {
|
|
48
|
+
path: string | undefined;
|
|
49
|
+
fromCwd: string | undefined;
|
|
50
|
+
fromModule: string | undefined;
|
|
51
|
+
};
|
|
17
52
|
/**
|
|
18
53
|
* Attach the position of the key a configuration error names.
|
|
19
54
|
*
|
|
@@ -9,6 +9,47 @@
|
|
|
9
9
|
* `overrides`: passing that inline is what silently did nothing (#76).
|
|
10
10
|
*/
|
|
11
11
|
export function sharedPrettierOptionsFor(file: string): object;
|
|
12
|
+
/**
|
|
13
|
+
* Where a resolved Prettier configuration disagrees with the shared one.
|
|
14
|
+
*
|
|
15
|
+
* The runner resolves each file's options as *either* the consumer's own config
|
|
16
|
+
* or {@link sharedPrettierOptionsFor}, never a merge. That is what bare Prettier
|
|
17
|
+
* does and it is the contract — but it means the conventions this package exists
|
|
18
|
+
* to publish hold by convention alone, and they lapse in two opposite directions
|
|
19
|
+
* (#133). A consumer that declares any config of its own gets whatever that
|
|
20
|
+
* config says: spread {@link PRETTIER_BASE} without the markdown override and
|
|
21
|
+
* every note reindents at 4, the reindentation the override was added to prevent
|
|
22
|
+
* (#76); write a partial `.prettierrc` such as `{"tabWidth": 2}` and
|
|
23
|
+
* `printWidth`, `trailingComma`, `experimentalTernaries` and the rest fall back
|
|
24
|
+
* to Prettier's own defaults. A consumer that declares *nothing* formats one way
|
|
25
|
+
* under this command and another under a bare `npx prettier`.
|
|
26
|
+
*
|
|
27
|
+
* This is the comparison that makes either absence visible. It is a **report,
|
|
28
|
+
* not a merge**: what a consumer declared still wins, and a deliberate local
|
|
29
|
+
* choice stays possible — it stops being silent, and nothing here fails a build
|
|
30
|
+
* over it.
|
|
31
|
+
*
|
|
32
|
+
* Every shared value is a primitive, so `!==` is the whole comparison. An option
|
|
33
|
+
* holding an object would need a deeper one, and the shared set has none —
|
|
34
|
+
* `overrides` is not compared, because `resolveConfig` has already applied and
|
|
35
|
+
* removed it by the time a configuration reaches this.
|
|
36
|
+
*
|
|
37
|
+
* @param {object|null|undefined} resolved - What `prettier.resolveConfig`
|
|
38
|
+
* returned for `file`, with the consumer's own `overrides` already applied.
|
|
39
|
+
* `null` — no configuration at all — reports every shared key as absent.
|
|
40
|
+
* @param {string} file - Path the options were resolved for. Decides whether
|
|
41
|
+
* {@link PRETTIER_MARKDOWN} is part of what is expected.
|
|
42
|
+
* @returns {Array<{key: string, shared: unknown, local: unknown}>} One entry per
|
|
43
|
+
* shared key the resolved configuration does not carry the value of, in the
|
|
44
|
+
* order {@link PRETTIER_BASE} declares them. `local` is `undefined` where the
|
|
45
|
+
* key is absent entirely, which is not the same finding as a key set to
|
|
46
|
+
* something else and is reported differently.
|
|
47
|
+
*/
|
|
48
|
+
export function sharedPrettierDivergence(resolved: object | null | undefined, file: string): Array<{
|
|
49
|
+
key: string;
|
|
50
|
+
shared: unknown;
|
|
51
|
+
local: unknown;
|
|
52
|
+
}>;
|
|
12
53
|
/**
|
|
13
54
|
* The prose conventions every content repository writes to — one Prettier
|
|
14
55
|
* configuration and one markdownlint rule set, declared here so a note
|
|
@@ -35,6 +35,42 @@ export function checkFormatting(root: string, opts?: {
|
|
|
35
35
|
checked: number;
|
|
36
36
|
written: string[];
|
|
37
37
|
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Report where a repository's own Prettier configuration parts from the shared
|
|
40
|
+
* one — or that it has none at all (#133).
|
|
41
|
+
*
|
|
42
|
+
* **Warnings, every one of them.** A consumer's config wins by design and this
|
|
43
|
+
* does not change that; it only refuses to let the divergence be silent, which
|
|
44
|
+
* is the whole of what the issue asks for. Turning any of this into an error
|
|
45
|
+
* would make the shared conventions mandatory, and they are a default.
|
|
46
|
+
*
|
|
47
|
+
* The no-configuration case is the sharper one and is reported even though the
|
|
48
|
+
* command itself behaves correctly there: with no config file the shared
|
|
49
|
+
* conventions reach `content-build format` and reach *nothing else*, so an
|
|
50
|
+
* editor's format-on-save and a bare `npx prettier --check .` apply Prettier's
|
|
51
|
+
* own defaults to the same tree, and the two take turns rewriting the same
|
|
52
|
+
* lines. That is not hypothetical — it is what the config files in
|
|
53
|
+
* `sohl-thalorna` and `sohl-kethira-basic` were added to stop.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} root - Repository to ask about.
|
|
56
|
+
* @param {object} [opts]
|
|
57
|
+
* @param {object} [opts.prettier] - The Prettier module, for tests.
|
|
58
|
+
* @returns {Promise<{findings: Array<{file?: string, severity: string,
|
|
59
|
+
* message: string}>, configFile: string|null}>} The findings and the config
|
|
60
|
+
* file they are about, which is `null` when the repository declares none. A
|
|
61
|
+
* finding about a missing file carries no `file`: #17's rule is to drop a
|
|
62
|
+
* field rather than invent one.
|
|
63
|
+
*/
|
|
64
|
+
export function checkPrettierConventions(root: string, opts?: {
|
|
65
|
+
prettier?: object | undefined;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
findings: Array<{
|
|
68
|
+
file?: string;
|
|
69
|
+
severity: string;
|
|
70
|
+
message: string;
|
|
71
|
+
}>;
|
|
72
|
+
configFile: string | null;
|
|
73
|
+
}>;
|
|
38
74
|
/**
|
|
39
75
|
* Lint a repository's markdown against the shared rule set.
|
|
40
76
|
*
|