@heroiclands/package-build 9.0.0 → 10.0.1
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 +721 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +437 -7
- package/content-config.mjs +259 -28
- package/docs/content-format.md +1418 -0
- package/engine/address-charset.mjs +62 -0
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +194 -4
- package/engine/content-address.mjs +4 -4
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +132 -56
- package/engine/content-lint.mjs +8 -1
- package/engine/diagnostics.mjs +33 -0
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-spec.mjs +49 -43
- package/engine/frontmatter-lint.mjs +351 -28
- package/engine/generate.mjs +32 -4
- package/engine/helpers.mjs +41 -29
- package/engine/ids.mjs +19 -1
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -5
- package/engine/kb-manifest.mjs +36 -7
- package/engine/map-notes.mjs +34 -18
- package/engine/note-claims.mjs +383 -0
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +39 -22
- package/engine/pack-router.mjs +17 -6
- package/engine/prose-lint.mjs +55 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scenes.mjs +19 -1
- package/engine/schema-check.mjs +347 -3
- package/engine/site-build.mjs +1 -1
- package/engine/site-index.mjs +38 -21
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +112 -80
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +67 -51
- package/package.json +6 -2
- package/sohl/actors.mjs +249 -36
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +3 -0
- package/sohl/items.mjs +110 -14
- package/sohl/note-schemas.mjs +11 -7
- package/types/content-config.d.mts +48 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +14 -1
- package/types/engine/helpers.d.mts +21 -13
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The declared tags a note of this type may carry, flattened.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} type - The note's type.
|
|
5
|
+
* @param {object} [groups] - The grouped declaration.
|
|
6
|
+
* @returns {readonly string[]} The tags, in declaration order.
|
|
7
|
+
*/
|
|
8
|
+
export function declaredTags(type: string, groups?: object): readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* The `data:` keys a note type may carry.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} type - The note's `type`.
|
|
13
|
+
* @param {Readonly<Record<string, TypeVocabulary>>} [vocabulary] - The registry
|
|
14
|
+
* to read, defaulting to {@link NOTE_VOCABULARY}.
|
|
15
|
+
* @returns {readonly DataFieldSpec[]|undefined} The declaration, or `undefined`
|
|
16
|
+
* when the type declares none — which is not the same as declaring an empty
|
|
17
|
+
* one, and is why the lint makes no claim rather than refusing every key.
|
|
18
|
+
*/
|
|
19
|
+
export function dataFields(type: string, vocabulary?: Readonly<Record<string, TypeVocabulary>>): readonly DataFieldSpec[] | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* The `subType` values a note type declares.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} type - The note's `type`.
|
|
24
|
+
* @param {Readonly<Record<string, TypeVocabulary>>} [vocabulary] - The registry
|
|
25
|
+
* to read, defaulting to {@link NOTE_VOCABULARY}.
|
|
26
|
+
* @returns {readonly string[]|null|undefined} The closed set; `null` when the
|
|
27
|
+
* type has a `subType` whose values are not yet enumerated; `undefined` when
|
|
28
|
+
* it has no `subType` at all — see {@link TypeVocabulary}.
|
|
29
|
+
*/
|
|
30
|
+
export function subTypes(type: string, vocabulary?: Readonly<Record<string, TypeVocabulary>>): readonly string[] | null | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Every note type this toolchain compiles, and the closed vocabulary it
|
|
33
|
+
* declares.
|
|
34
|
+
*
|
|
35
|
+
* Taken from the content-format specification, one `### type:` section per
|
|
36
|
+
* entry. Where the specification and the shape notes are authored in today
|
|
37
|
+
* disagree, the specification wins on the **name** — that is what a `data:`
|
|
38
|
+
* key will be called — and the disagreement is recorded on the field rather
|
|
39
|
+
* than resolved silently.
|
|
40
|
+
*
|
|
41
|
+
* @type {Readonly<Record<string, TypeVocabulary>>}
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* The tags that **classify** a note, grouped by what they classify (#172).
|
|
45
|
+
*
|
|
46
|
+
* `tags:` lives at the open top level and most tags belong there: a theme, a
|
|
47
|
+
* region, a working state is the author's own and this build has no opinion
|
|
48
|
+
* about it. A classifying tag is different, because something queries it — a
|
|
49
|
+
* settlement tagged `village` appears in the list of villages and an untagged
|
|
50
|
+
* one does not, so `vilage` does not merely look wrong, it removes the note from
|
|
51
|
+
* an index while the index still renders a table that looks complete.
|
|
52
|
+
*
|
|
53
|
+
* **This list is not a closed set.** An unrecognised tag is legal, because the
|
|
54
|
+
* region is open; what is reported is a **near miss** — a tag close enough to a
|
|
55
|
+
* declared one to be a typo of it.
|
|
56
|
+
*
|
|
57
|
+
* **Each group names the types it applies to**, and that scope is what makes the
|
|
58
|
+
* check sound rather than noisy. A place's kinds are only a place's: `azravan`
|
|
59
|
+
* on a faith, `barter` on an economy note and `secret` on three lore notes all
|
|
60
|
+
* sit within a typo's distance of `caravan`, `border` and `sacred`, and not one
|
|
61
|
+
* is a mistake. Checked against every group at once the rule was wrong on every
|
|
62
|
+
* note it touched; scoped to the type it is wrong on none. `types: null` is a
|
|
63
|
+
* group any note may carry.
|
|
64
|
+
*
|
|
65
|
+
* Kind and character are separate groups because one slot could not hold both: a
|
|
66
|
+
* fishing village is a `village` that is `fishing`, and the single-valued field
|
|
67
|
+
* this replaced had to spell it `Fishing Village` as a value of its own.
|
|
68
|
+
*/
|
|
69
|
+
export const DECLARED_TAGS: Readonly<{
|
|
70
|
+
/** What a place *is*. */
|
|
71
|
+
placeKind: Readonly<{
|
|
72
|
+
types: string[];
|
|
73
|
+
tags: readonly string[];
|
|
74
|
+
}>;
|
|
75
|
+
/** What a place is known for. */
|
|
76
|
+
placeCharacter: Readonly<{
|
|
77
|
+
types: string[];
|
|
78
|
+
tags: readonly string[];
|
|
79
|
+
}>;
|
|
80
|
+
/** A place's scale, where the subtype does not distinguish it. */
|
|
81
|
+
placeScale: Readonly<{
|
|
82
|
+
types: string[];
|
|
83
|
+
tags: readonly string[];
|
|
84
|
+
}>;
|
|
85
|
+
/** Which kind of body a being belongs to — a station rather than a rank. */
|
|
86
|
+
beingStation: Readonly<{
|
|
87
|
+
types: string[];
|
|
88
|
+
tags: readonly string[];
|
|
89
|
+
}>;
|
|
90
|
+
/** A note's working state, which any note may carry. */
|
|
91
|
+
state: Readonly<{
|
|
92
|
+
types: null;
|
|
93
|
+
tags: readonly string[];
|
|
94
|
+
}>;
|
|
95
|
+
}>;
|
|
96
|
+
export const NOTE_VOCABULARY: Readonly<{
|
|
97
|
+
being: Readonly<{
|
|
98
|
+
subTypes: null;
|
|
99
|
+
data: readonly DataFieldSpec[];
|
|
100
|
+
}>;
|
|
101
|
+
affiliation: Readonly<{
|
|
102
|
+
subTypes: readonly string[];
|
|
103
|
+
data: readonly DataFieldSpec[];
|
|
104
|
+
}>;
|
|
105
|
+
affliction: Readonly<{
|
|
106
|
+
subTypes: readonly string[];
|
|
107
|
+
data: readonly DataFieldSpec[];
|
|
108
|
+
}>;
|
|
109
|
+
armorgear: Readonly<{
|
|
110
|
+
data: readonly DataFieldSpec[];
|
|
111
|
+
}>;
|
|
112
|
+
attribute: Readonly<{
|
|
113
|
+
data: readonly DataFieldSpec[];
|
|
114
|
+
}>;
|
|
115
|
+
concoctiongear: Readonly<{
|
|
116
|
+
subTypes: readonly string[];
|
|
117
|
+
data: readonly (DataFieldSpec | Readonly<{
|
|
118
|
+
describe: "How many of the thing there are; one when unstated.";
|
|
119
|
+
shape: "number";
|
|
120
|
+
kind: "number";
|
|
121
|
+
name: "quantity";
|
|
122
|
+
}>)[];
|
|
123
|
+
}>;
|
|
124
|
+
containergear: Readonly<{
|
|
125
|
+
data: readonly DataFieldSpec[];
|
|
126
|
+
}>;
|
|
127
|
+
miscgear: Readonly<{
|
|
128
|
+
data: readonly (DataFieldSpec | Readonly<{
|
|
129
|
+
describe: "How many of the thing there are; one when unstated.";
|
|
130
|
+
shape: "number";
|
|
131
|
+
kind: "number";
|
|
132
|
+
name: "quantity";
|
|
133
|
+
}>)[];
|
|
134
|
+
}>;
|
|
135
|
+
mystery: Readonly<{
|
|
136
|
+
subTypes: readonly string[];
|
|
137
|
+
data: readonly DataFieldSpec[];
|
|
138
|
+
}>;
|
|
139
|
+
mysticalability: Readonly<{
|
|
140
|
+
subTypes: readonly string[];
|
|
141
|
+
data: readonly DataFieldSpec[];
|
|
142
|
+
}>;
|
|
143
|
+
projectilegear: Readonly<{
|
|
144
|
+
subTypes: readonly string[];
|
|
145
|
+
data: readonly (DataFieldSpec | Readonly<{
|
|
146
|
+
describe: "How many of the thing there are; one when unstated.";
|
|
147
|
+
shape: "number";
|
|
148
|
+
kind: "number";
|
|
149
|
+
name: "quantity";
|
|
150
|
+
}>)[];
|
|
151
|
+
}>;
|
|
152
|
+
skill: Readonly<{
|
|
153
|
+
subTypes: readonly string[];
|
|
154
|
+
data: readonly DataFieldSpec[];
|
|
155
|
+
}>;
|
|
156
|
+
trauma: Readonly<{
|
|
157
|
+
subTypes: readonly string[];
|
|
158
|
+
data: readonly DataFieldSpec[];
|
|
159
|
+
}>;
|
|
160
|
+
weapongear: Readonly<{
|
|
161
|
+
data: readonly DataFieldSpec[];
|
|
162
|
+
}>;
|
|
163
|
+
doc: Readonly<{
|
|
164
|
+
subTypes: readonly string[];
|
|
165
|
+
data: readonly never[];
|
|
166
|
+
}>;
|
|
167
|
+
macro: Readonly<{
|
|
168
|
+
data: readonly never[];
|
|
169
|
+
}>;
|
|
170
|
+
homepage: Readonly<{
|
|
171
|
+
data: readonly never[];
|
|
172
|
+
}>;
|
|
173
|
+
map: Readonly<{
|
|
174
|
+
subTypes: readonly string[];
|
|
175
|
+
data: readonly ({
|
|
176
|
+
describe: string;
|
|
177
|
+
shape: "string";
|
|
178
|
+
kind: "string";
|
|
179
|
+
name: string;
|
|
180
|
+
} | {
|
|
181
|
+
describe: string;
|
|
182
|
+
shape: "list";
|
|
183
|
+
kind: "list";
|
|
184
|
+
name: string;
|
|
185
|
+
} | {
|
|
186
|
+
describe: string;
|
|
187
|
+
shape: "number";
|
|
188
|
+
kind: "number";
|
|
189
|
+
name: string;
|
|
190
|
+
} | {
|
|
191
|
+
describe: string;
|
|
192
|
+
shape: "as authored";
|
|
193
|
+
name: string;
|
|
194
|
+
})[];
|
|
195
|
+
}>;
|
|
196
|
+
}>;
|
|
197
|
+
/**
|
|
198
|
+
* One `data:` key a note type may carry.
|
|
199
|
+
*
|
|
200
|
+
* A deliberate subset of {@link import ("./field-spec.mjs").FieldSpec}: no `to`,
|
|
201
|
+
* because nothing here builds anything yet. Reading `data.*` through into a
|
|
202
|
+
* document's `system` block is the passthrough slice (#126), and claiming an
|
|
203
|
+
* emitted path this does not produce would be a lie in the one place a reader
|
|
204
|
+
* would trust it.
|
|
205
|
+
*/
|
|
206
|
+
export type DataFieldSpec = {
|
|
207
|
+
/**
|
|
208
|
+
* - The key under `data:`, dotted for a nested one
|
|
209
|
+
* (`charges.value`).
|
|
210
|
+
*/
|
|
211
|
+
name: string;
|
|
212
|
+
/**
|
|
213
|
+
* - The value's
|
|
214
|
+
* shape, for the lint. Absent means no claim is made about the value — which
|
|
215
|
+
* is the honest answer wherever the specification's stated shape and the
|
|
216
|
+
* shape notes are authored in today disagree.
|
|
217
|
+
*/
|
|
218
|
+
kind?: "string" | "number" | "boolean" | "map" | "list" | undefined;
|
|
219
|
+
/**
|
|
220
|
+
* - Human-readable shape, for a finding and for
|
|
221
|
+
* documentation.
|
|
222
|
+
*/
|
|
223
|
+
shape?: string | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* - One line, for the author-facing reference.
|
|
226
|
+
*/
|
|
227
|
+
describe: string;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* What one note type declares.
|
|
231
|
+
*
|
|
232
|
+
* `subTypes` is three-valued, and the difference matters:
|
|
233
|
+
*
|
|
234
|
+
* - **omitted** — the type has no `subType` at all, and a note carrying one is
|
|
235
|
+
* a finding. A `weapon` is the deliberate case: SoHL distinguishes a
|
|
236
|
+
* weapon's uses by strike mode rather than by kind.
|
|
237
|
+
* - **`null`** — the type has a `subType` whose values the specification does
|
|
238
|
+
* not yet enumerate. Presence is permitted and the value is unchecked.
|
|
239
|
+
* - **a list** — the closed set of values, and anything else is a finding.
|
|
240
|
+
*/
|
|
241
|
+
export type TypeVocabulary = {
|
|
242
|
+
/**
|
|
243
|
+
* - The `data:` keys, closed.
|
|
244
|
+
*/
|
|
245
|
+
data: readonly DataFieldSpec[];
|
|
246
|
+
/**
|
|
247
|
+
* - The top-level `subType`
|
|
248
|
+
* values, as above.
|
|
249
|
+
*/
|
|
250
|
+
subTypes?: readonly string[] | null | undefined;
|
|
251
|
+
};
|
|
@@ -46,9 +46,10 @@ export function locateConfigError(err: unknown, configPath?: string): unknown;
|
|
|
46
46
|
* - **`rootDir`** is the configuration's own directory, always. A data file
|
|
47
47
|
* cannot write `import.meta.dirname`, and any absolute path it wrote instead
|
|
48
48
|
* would be one machine's — so authoring it is rejected rather than honoured.
|
|
49
|
-
* - **`itemBuilders`** is a *name* (`sohl`),
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* - **`itemBuilders`** is a *name* (`sohl`) — or a list of names, for a tree
|
|
50
|
+
* feeding more than one system (#58) — resolved against the built-in
|
|
51
|
+
* registries. A registry's name is the system it belongs to. A registry of a
|
|
52
|
+
* consumer's own is code, and code goes in an `.mjs` configuration.
|
|
52
53
|
* - **`stats.systemVersion`** is derived from the adjacent `package.json` when
|
|
53
54
|
* the configuration does not state it. Stating it is still allowed: a
|
|
54
55
|
* repository shipping content *for* another package (a module declaring
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @param {readonly object[]} packs - The resolved `packs` list from
|
|
8
8
|
* `defineConfig`.
|
|
9
|
-
* @returns {{resolve: (fm: object, docType: string) => string,
|
|
10
|
-
* resolveOrNull: (fm: object, docType: string) => string|undefined,
|
|
9
|
+
* @returns {{resolve: (fm: object, docType: string, system?: string) => string,
|
|
10
|
+
* resolveOrNull: (fm: object, docType: string, system?: string) => string|undefined,
|
|
11
11
|
* packsOfType: (docType: string) => string[],
|
|
12
12
|
* defaultOf: (docType: string) => string|undefined}} The router.
|
|
13
13
|
*/
|
|
14
14
|
export function createPackRouter(packs: readonly object[]): {
|
|
15
|
-
resolve: (fm: object, docType: string) => string;
|
|
16
|
-
resolveOrNull: (fm: object, docType: string) => string | undefined;
|
|
15
|
+
resolve: (fm: object, docType: string, system?: string) => string;
|
|
16
|
+
resolveOrNull: (fm: object, docType: string, system?: string) => string | undefined;
|
|
17
17
|
packsOfType: (docType: string) => string[];
|
|
18
18
|
defaultOf: (docType: string) => string | undefined;
|
|
19
19
|
};
|
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
* @param {readonly string[]} [opts.paths] - Files or directories to check
|
|
13
13
|
* instead of the whole root.
|
|
14
14
|
* @param {boolean} [opts.write=false] - Rewrite unformatted files in place
|
|
15
|
-
* rather than reporting them.
|
|
15
|
+
* rather than reporting them. Each file is formatted to a fixpoint (up to
|
|
16
|
+
* {@link MAX_FORMAT_PASSES} passes), so a written tree is one a second run
|
|
17
|
+
* leaves alone; a file that will not converge is reported and left unchanged
|
|
18
|
+
* (#125).
|
|
16
19
|
* @param {object} [opts.prettier] - The Prettier module, for tests.
|
|
17
20
|
* @returns {Promise<{findings: Array<{file: string, severity: string,
|
|
18
21
|
* message: string}>, checked: number, written: string[]}>} The findings, how
|
|
19
|
-
* many files were considered, and what was rewritten.
|
|
22
|
+
* many files were considered, and what was rewritten. `--write` reports
|
|
23
|
+
* findings too — a file it cannot parse, or cannot format to a fixpoint.
|
|
20
24
|
*/
|
|
21
25
|
export function checkFormatting(root: string, opts?: {
|
|
22
26
|
paths?: readonly string[] | undefined;
|
|
@@ -43,12 +43,83 @@ export function assertNoDraftField(fm: object | null | undefined, { file, absPat
|
|
|
43
43
|
* both refusals need it and a second copy is a second thing to keep correct.
|
|
44
44
|
*
|
|
45
45
|
* @param {string|undefined} absPath - The note's file.
|
|
46
|
-
* @param {string} key - The
|
|
46
|
+
* @param {string} key - The frontmatter key.
|
|
47
|
+
* @param {string} [value] - When given, prefer the occurrence whose line also
|
|
48
|
+
* carries this text — so a finding about one entry of a block opens on that
|
|
49
|
+
* entry rather than on the key that introduces it.
|
|
47
50
|
* @returns {{line?: number, column?: number}|undefined} Spreadable position
|
|
48
51
|
* fields, dropped rather than guessed when the file cannot be read or the key
|
|
49
52
|
* cannot be found — as `formatDiagnostic` requires.
|
|
50
53
|
*/
|
|
51
|
-
export function locateFrontmatterKey(absPath: string | undefined, key: string): {
|
|
54
|
+
export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string): {
|
|
52
55
|
line?: number;
|
|
53
56
|
column?: number;
|
|
54
57
|
} | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* What a note writing a renamed field is told, in one place.
|
|
60
|
+
*
|
|
61
|
+
* Shared by the compile-time report and the frontmatter lint, because an author
|
|
62
|
+
* meets whichever runs first and they should read the same. It names the key to
|
|
63
|
+
* write rather than a value to correct — no value makes the retired spelling
|
|
64
|
+
* right — and it says the note compiles either way, so a reader knows this is a
|
|
65
|
+
* rename to schedule rather than a build to unbreak.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} retired - The spelling the note used.
|
|
68
|
+
* @param {string} current - What to write instead.
|
|
69
|
+
* @param {string} [file] - The note's path, named in the message. Omit it where
|
|
70
|
+
* the caller emits through a diagnostic, whose locator already starts the
|
|
71
|
+
* line — repeating it prints the path twice.
|
|
72
|
+
* @returns {string} The message, unpunctuated at the end as a finding is.
|
|
73
|
+
*/
|
|
74
|
+
export function retiredAliasMessage(retired: string, current: string, file?: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Whether a note writes the retired spelling of a field, wherever it put it.
|
|
77
|
+
*
|
|
78
|
+
* Both regions are searched, because {@link sohlField} reads both: a note that
|
|
79
|
+
* moved the key to the top level without renaming it has done half the
|
|
80
|
+
* migration, and should be told so rather than passing in silence.
|
|
81
|
+
*
|
|
82
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
83
|
+
* @param {string} current - The field's current name.
|
|
84
|
+
* @returns {boolean} Whether the retired spelling is declared.
|
|
85
|
+
*/
|
|
86
|
+
export function declaresRetiredAlias(fm: object | null | undefined, current: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Read a field that has a retired spelling, the current name winning.
|
|
89
|
+
*
|
|
90
|
+
* This is the whole of the retirement window's behaviour, in one function, so
|
|
91
|
+
* the compiler and the linter cannot disagree about which value a note carries.
|
|
92
|
+
* Resolution within each spelling is {@link sohlField}'s — the `sohl:` block
|
|
93
|
+
* first, then the note's top level — so a renamed field keeps working wherever
|
|
94
|
+
* it was already written while the canonical home is the top level.
|
|
95
|
+
*
|
|
96
|
+
* A blank value counts as absent: `img:` cleared in an editor means the note
|
|
97
|
+
* names no art there, and falling through to the retired spelling is what an
|
|
98
|
+
* author part-way through the rename means by it.
|
|
99
|
+
*
|
|
100
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
101
|
+
* @param {string} current - The field's current name.
|
|
102
|
+
* @returns {any} The value, or `undefined` when neither spelling carries one.
|
|
103
|
+
*/
|
|
104
|
+
export function readAliasedField(fm: object | null | undefined, current: string): any;
|
|
105
|
+
/**
|
|
106
|
+
* The current field name a retired spelling was renamed to.
|
|
107
|
+
*
|
|
108
|
+
* Keyed by the **current** name, because that is what a type's schema declares
|
|
109
|
+
* and what every reader asks for; the value is the spelling still honoured.
|
|
110
|
+
* The table is therefore scoped by the schema without saying so twice: an alias
|
|
111
|
+
* applies to a note only where that note's type declares the current field, so
|
|
112
|
+
* `image` is retired on a map — which declares `img` — and remains an unknown
|
|
113
|
+
* key anywhere else.
|
|
114
|
+
*
|
|
115
|
+
* **`img` (#142).** Every note type names its artwork `img`, at the note's top
|
|
116
|
+
* level, and resolves it the same way. A map alone named its background art
|
|
117
|
+
* `image` and read it out of the `sohl:` block — two spellings for one idea,
|
|
118
|
+
* with nothing to reconcile them, and a specification that had to hedge rather
|
|
119
|
+
* than state a rule. Art is not system-specific: a Scene is a core Foundry
|
|
120
|
+
* document and HM3 would want the identical one, so the field belongs beside
|
|
121
|
+
* every other note's `img`, not inside a system block.
|
|
122
|
+
*
|
|
123
|
+
* @type {Readonly<Record<string, string>>}
|
|
124
|
+
*/
|
|
125
|
+
export const RETIRED_FIELD_ALIASES: Readonly<Record<string, string>>;
|
|
@@ -85,6 +85,56 @@ export function compareFields({ builders, artifact, documentType, subtypeOf, }:
|
|
|
85
85
|
unemitted: object[];
|
|
86
86
|
skipped: string[];
|
|
87
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* What a **compiled document** carries in `system`, against what the receiving
|
|
90
|
+
* subtype declares (#155).
|
|
91
|
+
*
|
|
92
|
+
* The third of the three checks, and the only one whose emitted set is
|
|
93
|
+
* *observed*. {@link compareFields} reads the `itemBuilders` declarations and
|
|
94
|
+
* {@link checkAuthoredSystemData} reads a note's `<system>.system`; between them
|
|
95
|
+
* they miss every key a compiler writes on its own initiative, which is not a
|
|
96
|
+
* residue — it is `shortcode`, `actionDefs`, `notes`, `docHtml` and `archetype`.
|
|
97
|
+
*
|
|
98
|
+
* **The keys come from the object the compiler built, after a JSON round trip.**
|
|
99
|
+
* That is exactly what the pack file receives, so a key whose value is
|
|
100
|
+
* `undefined` — dropped by `JSON.stringify`, never written, nothing for Foundry
|
|
101
|
+
* to discard — is correctly not a finding. Reading the assembled block is also
|
|
102
|
+
* the only derivation that cannot go stale: a compiler that grows a key is
|
|
103
|
+
* checked on the next build without anyone remembering to add it to a list.
|
|
104
|
+
*
|
|
105
|
+
* **Authored paths are left alone.** A note's own `<system>.system` is reported
|
|
106
|
+
* by {@link checkAuthoredSystemData}, which can point at the line the author
|
|
107
|
+
* wrote; reporting it again here would be the same defect twice, once without a
|
|
108
|
+
* position.
|
|
109
|
+
*
|
|
110
|
+
* **A subtree the artifact does not describe is not checked**, rather than
|
|
111
|
+
* reported wholesale — see {@link enumeratedContainers} for the
|
|
112
|
+
* `TypedSchemaField` case that makes the distinction load-bearing.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} opts
|
|
115
|
+
* @param {object} opts.system - The `system` block the compiler assembled.
|
|
116
|
+
* @param {SchemaArtifact} opts.artifact - The receiving system's schemas.
|
|
117
|
+
* @param {string} opts.documentType - `Item`, `Actor`, …
|
|
118
|
+
* @param {string} opts.subtype - The document subtype being emitted.
|
|
119
|
+
* @param {string} opts.type - The content type whose note produced it, for the
|
|
120
|
+
* message.
|
|
121
|
+
* @param {readonly {to?: string}[]} [opts.fields] - The type's field
|
|
122
|
+
* declaration, which decides a finding's {@link EmissionFinding.origin}.
|
|
123
|
+
* @param {ReadonlySet<string>} [opts.authored] - Paths the note authored, left
|
|
124
|
+
* to the note-side check.
|
|
125
|
+
* @returns {EmissionFinding[]} One per undeclared path, shallowest-first.
|
|
126
|
+
*/
|
|
127
|
+
export function compareEmittedSystem({ system, artifact, documentType, subtype, type, fields, authored, }: {
|
|
128
|
+
system: object;
|
|
129
|
+
artifact: SchemaArtifact;
|
|
130
|
+
documentType: string;
|
|
131
|
+
subtype: string;
|
|
132
|
+
type: string;
|
|
133
|
+
fields?: readonly {
|
|
134
|
+
to?: string;
|
|
135
|
+
}[] | undefined;
|
|
136
|
+
authored?: ReadonlySet<string> | undefined;
|
|
137
|
+
}): EmissionFinding[];
|
|
88
138
|
/**
|
|
89
139
|
* The published schema this build should check itself against, or `null`.
|
|
90
140
|
*
|
|
@@ -140,6 +190,107 @@ export function undeclaredMessage(finding: object): string;
|
|
|
140
190
|
* @returns {string} The message.
|
|
141
191
|
*/
|
|
142
192
|
export function unemittedMessage(finding: object): string;
|
|
193
|
+
/**
|
|
194
|
+
* What an author is told about an emitted key the target system does not
|
|
195
|
+
* define — in the terms of whoever can actually fix it.
|
|
196
|
+
*
|
|
197
|
+
* The failure is identical in both cases and the remedies are not, which is
|
|
198
|
+
* why the two are told apart at all:
|
|
199
|
+
*
|
|
200
|
+
* | origin | who writes it | what fixes it |
|
|
201
|
+
* | --- | --- | --- |
|
|
202
|
+
* | `builder` | a `fields:` entry in this repository's `itemBuilders` | change the field's `to`, or get the system to declare it |
|
|
203
|
+
* | `compiler` | this package, on every document of the subtype | **nothing here** — the system must declare it, or this package must be pinned to a build that does not write it |
|
|
204
|
+
*
|
|
205
|
+
* A `compiler` finding is the one worth spelling out, because the obvious first
|
|
206
|
+
* move — go and look for the field in `itemBuilders` — leads nowhere: there is
|
|
207
|
+
* no declaration to correct. It means the build is running ahead of the system
|
|
208
|
+
* it compiles for, and the version named in the message is what says so.
|
|
209
|
+
*
|
|
210
|
+
* @param {EmissionFinding} finding - One entry from
|
|
211
|
+
* {@link compareEmittedSystem}.
|
|
212
|
+
* @returns {string} The message.
|
|
213
|
+
*/
|
|
214
|
+
export function emittedUndeclaredMessage(finding: EmissionFinding): string;
|
|
215
|
+
/**
|
|
216
|
+
* What a note authors under `<system>.system`, against what the receiving
|
|
217
|
+
* subtype declares (#58).
|
|
218
|
+
*
|
|
219
|
+
* The **note-side** half of the check `compareFields` performs on the
|
|
220
|
+
* declarations. A field list is checked once for the whole build because it is
|
|
221
|
+
* the same for every document; an authored `system` block is a property of one
|
|
222
|
+
* note, so it is checked where that note is compiled and reported against that
|
|
223
|
+
* note's file.
|
|
224
|
+
*
|
|
225
|
+
* It is the same failure either way, and the reason both halves exist: Foundry
|
|
226
|
+
* discards an unknown `system` key at construction and says nothing, so a
|
|
227
|
+
* mistyped path is lost at load while the build reports success.
|
|
228
|
+
*
|
|
229
|
+
* **Silent where there is nothing to check against.** A module pinning a system
|
|
230
|
+
* version released before the artifact existed, or a subtype the artifact does
|
|
231
|
+
* not name, produces no findings — the same stance `compareFields` takes, where
|
|
232
|
+
* an unknown subtype is a routing question rather than a field one. The
|
|
233
|
+
* whole-build check in `content-build lint` is where a missing artifact is said
|
|
234
|
+
* out loud, once, instead of per note.
|
|
235
|
+
*
|
|
236
|
+
* @param {object} fm - The note's frontmatter.
|
|
237
|
+
* @param {object} opts
|
|
238
|
+
* @param {string} opts.block - The system block to read, e.g. `"sohl"`.
|
|
239
|
+
* @param {string} opts.documentType - `Item`, `Actor`, …
|
|
240
|
+
* @param {string} opts.subType - The document subtype the note compiles into.
|
|
241
|
+
* @param {object} [opts.config] - The resolved build configuration.
|
|
242
|
+
* @returns {{path: string, message: string}[]} One finding per undeclared path,
|
|
243
|
+
* shallowest-first.
|
|
244
|
+
*/
|
|
245
|
+
export function checkAuthoredSystemData(fm: object, { block, documentType, subType, config }: {
|
|
246
|
+
block: string;
|
|
247
|
+
documentType: string;
|
|
248
|
+
subType: string;
|
|
249
|
+
config?: object | undefined;
|
|
250
|
+
}): {
|
|
251
|
+
path: string;
|
|
252
|
+
message: string;
|
|
253
|
+
}[];
|
|
254
|
+
/**
|
|
255
|
+
* The `system` block a compiler just assembled, against what the receiving
|
|
256
|
+
* subtype declares (#155).
|
|
257
|
+
*
|
|
258
|
+
* The build-time face of {@link compareEmittedSystem}: it resolves the schema
|
|
259
|
+
* the way every other check here does — the system's own committed artifact, or
|
|
260
|
+
* the cached one from the release a module pins — and attaches the message a
|
|
261
|
+
* reader sees.
|
|
262
|
+
*
|
|
263
|
+
* **Silent where there is nothing to check against**, exactly as its two
|
|
264
|
+
* siblings are: a module pinning a system version released before the artifact
|
|
265
|
+
* existed, or a subtype the artifact does not name, produces no findings.
|
|
266
|
+
* `content-build lint` is where a missing artifact is said out loud, once.
|
|
267
|
+
*
|
|
268
|
+
* @param {object} system - The `system` block the compiler produced.
|
|
269
|
+
* @param {object} opts
|
|
270
|
+
* @param {object} opts.fm - The note's frontmatter, for the authored paths this
|
|
271
|
+
* check leaves to {@link checkAuthoredSystemData}.
|
|
272
|
+
* @param {string} opts.block - The system block to read, e.g. `"sohl"`.
|
|
273
|
+
* @param {string} opts.documentType - `Item`, `Actor`, …
|
|
274
|
+
* @param {string} opts.subType - The document subtype the note compiles into.
|
|
275
|
+
* @param {string} opts.type - The note's content type, for the message.
|
|
276
|
+
* @param {readonly {to?: string}[]} [opts.fields] - The type's field
|
|
277
|
+
* declaration, which decides each finding's origin.
|
|
278
|
+
* @param {object} [opts.config] - The resolved build configuration.
|
|
279
|
+
* @returns {(EmissionFinding & {message: string})[]} One per undeclared path.
|
|
280
|
+
*/
|
|
281
|
+
export function checkEmittedSystemData(system: object, { fm, block, documentType, subType, type, fields, config }: {
|
|
282
|
+
fm: object;
|
|
283
|
+
block: string;
|
|
284
|
+
documentType: string;
|
|
285
|
+
subType: string;
|
|
286
|
+
type: string;
|
|
287
|
+
fields?: readonly {
|
|
288
|
+
to?: string;
|
|
289
|
+
}[] | undefined;
|
|
290
|
+
config?: object | undefined;
|
|
291
|
+
}): (EmissionFinding & {
|
|
292
|
+
message: string;
|
|
293
|
+
})[];
|
|
143
294
|
/**
|
|
144
295
|
* The artifact version this module reads.
|
|
145
296
|
*
|
|
@@ -174,3 +325,34 @@ export type SchemaArtifact = {
|
|
|
174
325
|
inherited: string[];
|
|
175
326
|
}>>;
|
|
176
327
|
};
|
|
328
|
+
/**
|
|
329
|
+
* One `system` key a compiled document carries that its subtype does not
|
|
330
|
+
* declare.
|
|
331
|
+
*/
|
|
332
|
+
export type EmissionFinding = {
|
|
333
|
+
/**
|
|
334
|
+
* - The content type whose note produced the document.
|
|
335
|
+
*/
|
|
336
|
+
type: string;
|
|
337
|
+
/**
|
|
338
|
+
* - The document subtype it compiled into.
|
|
339
|
+
*/
|
|
340
|
+
subtype: string;
|
|
341
|
+
/**
|
|
342
|
+
* - `Item`, `Actor`, …
|
|
343
|
+
*/
|
|
344
|
+
documentType: string;
|
|
345
|
+
/**
|
|
346
|
+
* - The undeclared path, dotted.
|
|
347
|
+
*/
|
|
348
|
+
field: string;
|
|
349
|
+
/**
|
|
350
|
+
* - The version checked against.
|
|
351
|
+
*/
|
|
352
|
+
systemVersion: string;
|
|
353
|
+
/**
|
|
354
|
+
* - What wrote it. See
|
|
355
|
+
* {@link emittedUndeclaredMessage}.
|
|
356
|
+
*/
|
|
357
|
+
origin: "builder" | "compiler";
|
|
358
|
+
};
|