@heroiclands/package-build 20.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 +476 -0
- package/CONTENT.md +185 -25
- package/README.md +43 -4
- package/bin/content-build.mjs +87 -2
- package/config.mjs +9 -1
- package/content-config.mjs +89 -18
- package/e2e.mjs +297 -3
- package/engine/content-charset.mjs +434 -0
- package/engine/content-icons.mjs +388 -0
- package/engine/foreign-catalog.mjs +109 -7
- package/engine/frontmatter-lint.mjs +191 -29
- package/engine/generate.mjs +5 -2
- package/engine/helpers.mjs +10 -1
- package/engine/index.mjs +6 -0
- package/engine/note-claims.mjs +208 -5
- 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/schema-extract.mjs +13 -0
- package/package.json +1 -1
- package/types/config.d.mts +7 -0
- package/types/e2e.d.mts +130 -3
- package/types/engine/content-charset.d.mts +127 -0
- package/types/engine/content-icons.d.mts +151 -0
- package/types/engine/foreign-catalog.d.mts +38 -2
- package/types/engine/frontmatter-lint.d.mts +146 -28
- package/types/engine/helpers.d.mts +8 -0
- package/types/engine/index.d.mts +2 -0
- package/types/engine/note-claims.d.mts +67 -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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 1 — the letters, and the two whitespace characters a file is made of.
|
|
3
|
+
*
|
|
4
|
+
* The Latin-1 range is split around `U+00D7` and `U+00F7` deliberately: `×` and
|
|
5
|
+
* `÷` sit inside the letter block but are operators, and they are admitted
|
|
6
|
+
* below in Tier 3 on their own merits rather than smuggled in as letters.
|
|
7
|
+
*
|
|
8
|
+
* @param {number} cp - A Unicode code point.
|
|
9
|
+
* @returns {boolean} Whether Tier 1 admits it.
|
|
10
|
+
*/
|
|
11
|
+
export function isLetterTier(cp: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the charset admits a code point anywhere in a note.
|
|
14
|
+
*
|
|
15
|
+
* @param {number} cp - A Unicode code point.
|
|
16
|
+
* @returns {boolean} Whether it is allowed outside a code fence.
|
|
17
|
+
*/
|
|
18
|
+
export function isAllowedCodePoint(cp: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a code point is diagram furniture, admitted inside a fence only.
|
|
21
|
+
*
|
|
22
|
+
* A fenced block is set in the mono face, and the mono face is not the book
|
|
23
|
+
* face — so the question "does the text font have this" is the wrong question
|
|
24
|
+
* to ask about a character in an ASCII-art org chart. All three ranges were
|
|
25
|
+
* confirmed present in DejaVu Sans Mono, the mono face Typst embeds.
|
|
26
|
+
*
|
|
27
|
+
* @param {number} cp - A Unicode code point.
|
|
28
|
+
* @returns {boolean} Whether a fence may carry it.
|
|
29
|
+
*/
|
|
30
|
+
export function isDiagramCodePoint(cp: number): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The reason a code point is refused.
|
|
33
|
+
*
|
|
34
|
+
* @param {number} cp - A Unicode code point.
|
|
35
|
+
* @returns {string} A clause naming what it is and what to do instead.
|
|
36
|
+
*/
|
|
37
|
+
export function refusalFor(cp: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* Every non-NFC run in a string, with the composed form it should have been.
|
|
40
|
+
*
|
|
41
|
+
* Reported as **runs** rather than as bare combining marks, because `y` plus
|
|
42
|
+
* `U+0301` is one authoring mistake and pointing at the accent alone would name
|
|
43
|
+
* the half the author did not type.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} text - File contents.
|
|
46
|
+
* @returns {Array<{sequence: string, composed: string, index: number}>} Each
|
|
47
|
+
* offending run, in the order it appears.
|
|
48
|
+
*/
|
|
49
|
+
export function decomposedRuns(text: string): Array<{
|
|
50
|
+
sequence: string;
|
|
51
|
+
composed: string;
|
|
52
|
+
index: number;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Check one file's text against the charset and the normalization rule.
|
|
56
|
+
*
|
|
57
|
+
* Findings are **deduplicated per character per line**: a 60-cell table of
|
|
58
|
+
* `━` is one mistake made once, and sixty findings would bury the other
|
|
59
|
+
* fifty-nine things wrong with the tree.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} text - The file's contents.
|
|
62
|
+
* @param {string} file - Path to report, relative to the tree.
|
|
63
|
+
* @returns {Array<{file: string, line: number, column: number,
|
|
64
|
+
* severity: "error", message: string}>} What is wrong, in file order.
|
|
65
|
+
*/
|
|
66
|
+
export function checkText(text: string, file: string): Array<{
|
|
67
|
+
file: string;
|
|
68
|
+
line: number;
|
|
69
|
+
column: number;
|
|
70
|
+
severity: "error";
|
|
71
|
+
message: string;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Walk a content tree and check every authored file in it.
|
|
75
|
+
*
|
|
76
|
+
* Dot-directories are skipped: `.obsidian` carries editor state, and a plugin
|
|
77
|
+
* manifest's CRLF line endings are not this tree's prose. That is not a
|
|
78
|
+
* theoretical exclusion — it was the first thing a run over `sohl-thalorna`
|
|
79
|
+
* reported before the skip existed.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} contentBase - Root of the content tree.
|
|
82
|
+
* @param {object} [opts]
|
|
83
|
+
* @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore
|
|
84
|
+
* in addition to the dot-directories always skipped.
|
|
85
|
+
* @param {readonly string[]} [opts.extensions] - File extensions to read.
|
|
86
|
+
* @returns {{findings: Array<{file: string, line: number, column: number,
|
|
87
|
+
* severity: "error", message: string}>, files: number}} The findings, and how
|
|
88
|
+
* many files produced them.
|
|
89
|
+
*/
|
|
90
|
+
export function lintContentCharset(contentBase: string, { skipDirectories, extensions }?: {
|
|
91
|
+
skipDirectories?: readonly string[] | undefined;
|
|
92
|
+
extensions?: readonly string[] | undefined;
|
|
93
|
+
}): {
|
|
94
|
+
findings: Array<{
|
|
95
|
+
file: string;
|
|
96
|
+
line: number;
|
|
97
|
+
column: number;
|
|
98
|
+
severity: "error";
|
|
99
|
+
message: string;
|
|
100
|
+
}>;
|
|
101
|
+
files: number;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Tier 2 — typography, enumerated one codepoint at a time.
|
|
105
|
+
*
|
|
106
|
+
* **Not the General Punctuation block.** `U+2000`–`U+206F` carries `U+200B`
|
|
107
|
+
* ZERO WIDTH SPACE, the `U+200E`/`U+200F` direction marks, the `U+2028`/`U+2029`
|
|
108
|
+
* separators, the `U+202A`–`U+202E` bidi overrides and `U+2060` WORD JOINER —
|
|
109
|
+
* precisely the invisibles this charset exists to refuse. Admitting the block
|
|
110
|
+
* to reach the em dash would admit all of them, so the ten that are wanted are
|
|
111
|
+
* named and the block is not.
|
|
112
|
+
*
|
|
113
|
+
* @type {ReadonlySet<number>}
|
|
114
|
+
*/
|
|
115
|
+
export const TYPOGRAPHY: ReadonlySet<number>;
|
|
116
|
+
/**
|
|
117
|
+
* Tier 3 — the notation the rules and price tables are written in.
|
|
118
|
+
*
|
|
119
|
+
* Every one of these is carried by seven or eight of the eight probed faces, so
|
|
120
|
+
* the tier costs nothing in font freedom. It is a separate tier from the
|
|
121
|
+
* typography above only because it is a separate argument: these earn their
|
|
122
|
+
* place by being *needed* — a Shock threshold reads `≥ 10`, a wall is `10′ ×
|
|
123
|
+
* 11′` — where the typography earns it by being unavoidable.
|
|
124
|
+
*
|
|
125
|
+
* @type {ReadonlySet<number>}
|
|
126
|
+
*/
|
|
127
|
+
export const NOTATION: ReadonlySet<number>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Look one name up.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} name - The name written between the colons, without `icon-`.
|
|
5
|
+
* @param {Record<string, object>} [registry] - Defaults to {@link DEFAULT_ICONS}.
|
|
6
|
+
* @returns {{style: string, icon: string, label: string}|null} The entry, or
|
|
7
|
+
* `null` when the registry does not declare it.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveIcon(name: string, registry?: Record<string, object>): {
|
|
10
|
+
style: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
label: string;
|
|
13
|
+
} | null;
|
|
14
|
+
/**
|
|
15
|
+
* The HTML the journals and the website emit — what the system already renders.
|
|
16
|
+
*
|
|
17
|
+
* Carries an accessible name rather than `aria-hidden`. The system's own
|
|
18
|
+
* templates hide their icons because a labelled parent element speaks for them;
|
|
19
|
+
* an icon dropped into a sentence has no such parent, and "the ☆ toggles it"
|
|
20
|
+
* read aloud as "the toggles it" is a sentence with a hole in it.
|
|
21
|
+
*
|
|
22
|
+
* @param {{style: string, icon: string, label: string}} entry - A registry entry.
|
|
23
|
+
* @returns {string} An `<i>` element.
|
|
24
|
+
*/
|
|
25
|
+
export function iconHtml(entry: {
|
|
26
|
+
style: string;
|
|
27
|
+
icon: string;
|
|
28
|
+
label: string;
|
|
29
|
+
}): string;
|
|
30
|
+
/**
|
|
31
|
+
* Every icon a string names, in the order written.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} text - Markdown source.
|
|
34
|
+
* @returns {Array<{name: string, index: number, raw: string}>} What it names.
|
|
35
|
+
*/
|
|
36
|
+
export function iconsIn(text: string): Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
index: number;
|
|
39
|
+
raw: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Report every icon a tree names that its registry does not declare.
|
|
43
|
+
*
|
|
44
|
+
* The whole point of a registry is that a typo is answerable, so this is the
|
|
45
|
+
* half that makes `:icon-stra:` a finding rather than three words of literal
|
|
46
|
+
* text nobody notices in a rendered page.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} text - The file's contents.
|
|
49
|
+
* @param {string} file - Path to report.
|
|
50
|
+
* @param {Record<string, object>} [registry] - Defaults to {@link DEFAULT_ICONS}.
|
|
51
|
+
* @returns {Array<{file: string, line: number, column: number,
|
|
52
|
+
* severity: "error", message: string}>} The unknown names.
|
|
53
|
+
*/
|
|
54
|
+
export function lintIcons(text: string, file: string, registry?: Record<string, object>): Array<{
|
|
55
|
+
file: string;
|
|
56
|
+
line: number;
|
|
57
|
+
column: number;
|
|
58
|
+
severity: "error";
|
|
59
|
+
message: string;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Refuse a registry that names a style Font Awesome Free does not ship.
|
|
63
|
+
*
|
|
64
|
+
* @param {Record<string, object>} registry - A package's icon table.
|
|
65
|
+
* @param {string} [where="icons"] - Where to say the fault is.
|
|
66
|
+
* @returns {Array<{severity: "error", message: string}>} What is wrong with it.
|
|
67
|
+
*/
|
|
68
|
+
export function checkIconRegistry(registry: Record<string, object>, where?: string): Array<{
|
|
69
|
+
severity: "error";
|
|
70
|
+
message: string;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Walk a content tree and report every icon name its registry does not declare.
|
|
74
|
+
*
|
|
75
|
+
* Its own walk rather than the charset check's, so both modules stay leaves
|
|
76
|
+
* with nothing imported between them. The cost is one extra pass over the tree,
|
|
77
|
+
* which is the cheaper half of a lint that already parses every note.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} contentBase - Root of the content tree.
|
|
80
|
+
* @param {object} [opts]
|
|
81
|
+
* @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore.
|
|
82
|
+
* @param {Record<string, object>} [opts.registry] - The package's icon table.
|
|
83
|
+
* @returns {{findings: Array<{file: string, line: number, column: number,
|
|
84
|
+
* severity: "error", message: string}>, files: number}} What it found.
|
|
85
|
+
*/
|
|
86
|
+
export function lintContentIcons(contentBase: string, { skipDirectories, registry }?: {
|
|
87
|
+
skipDirectories?: readonly string[] | undefined;
|
|
88
|
+
registry?: Record<string, object> | undefined;
|
|
89
|
+
}): {
|
|
90
|
+
findings: Array<{
|
|
91
|
+
file: string;
|
|
92
|
+
line: number;
|
|
93
|
+
column: number;
|
|
94
|
+
severity: "error";
|
|
95
|
+
message: string;
|
|
96
|
+
}>;
|
|
97
|
+
files: number;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* A markdown-it plugin rendering `:icon-name:` inline.
|
|
101
|
+
*
|
|
102
|
+
* An unknown name is left **exactly as written** rather than dropped. The name
|
|
103
|
+
* is reported by {@link lintIcons}, and a rendered page that still shows
|
|
104
|
+
* `:icon-stra:` is how the author finds it without reading a log.
|
|
105
|
+
*
|
|
106
|
+
* @param {Record<string, object>} [registry] - Defaults to {@link DEFAULT_ICONS}.
|
|
107
|
+
* @returns {(md: object) => void} A markdown-it plugin.
|
|
108
|
+
*/
|
|
109
|
+
export function iconPlugin(registry?: Record<string, object>): (md: object) => void;
|
|
110
|
+
/**
|
|
111
|
+
* The Font Awesome styles a registry entry may name.
|
|
112
|
+
*
|
|
113
|
+
* Free ships these three and no others, so a `light` or `duotone` entry would
|
|
114
|
+
* name a glyph the shipped font does not contain — refused here rather than
|
|
115
|
+
* discovered as a blank space in a printed book.
|
|
116
|
+
*
|
|
117
|
+
* @type {readonly string[]}
|
|
118
|
+
*/
|
|
119
|
+
export const ICON_STYLES: readonly string[];
|
|
120
|
+
/**
|
|
121
|
+
* The icons the user guide already depicts, under the names it should call them.
|
|
122
|
+
*
|
|
123
|
+
* Each entry was read off the interface it describes rather than invented: the
|
|
124
|
+
* `star`/`star-outline` pair is the filled and hollow star the mastery row and
|
|
125
|
+
* the improve flag draw, and `edit` is the pencil the formula editor opens
|
|
126
|
+
* from. The names are what a *writer* would reach for — `delete`, not
|
|
127
|
+
* `trash-can` — because the writer is the one typing them; the Font Awesome
|
|
128
|
+
* spelling is this table's business, not theirs.
|
|
129
|
+
*
|
|
130
|
+
* @type {Readonly<Record<string, {style: string, icon: string, label: string}>>}
|
|
131
|
+
*/
|
|
132
|
+
export const DEFAULT_ICONS: Readonly<Record<string, {
|
|
133
|
+
style: string;
|
|
134
|
+
icon: string;
|
|
135
|
+
label: string;
|
|
136
|
+
}>>;
|
|
137
|
+
/**
|
|
138
|
+
* The shape a note writes, and the one this module claims.
|
|
139
|
+
*
|
|
140
|
+
* The `icon-` prefix is what keeps it out of the way of an emoji shortcode: a
|
|
141
|
+
* surface that also renders `:smile:` can tell the two apart without a lookup,
|
|
142
|
+
* and a reader can tell what `:icon-star:` is without knowing this module
|
|
143
|
+
* exists. Names are lowercase, digits and hyphens — the charset an address
|
|
144
|
+
* segment already uses (#59), so nothing new has to be explained.
|
|
145
|
+
*
|
|
146
|
+
* Not `:name[content]`. That is remark-directive syntax, and this toolchain
|
|
147
|
+
* parses with markdown-it; a directive would render as its own literal text.
|
|
148
|
+
*
|
|
149
|
+
* @type {RegExp}
|
|
150
|
+
*/
|
|
151
|
+
export const ICON_PATTERN: RegExp;
|
|
@@ -32,6 +32,25 @@ export function catalogDir(config: object, id: string, version: string): string;
|
|
|
32
32
|
* @returns {string} The path, whether or not it exists.
|
|
33
33
|
*/
|
|
34
34
|
export function cachedSchemaPath(config: object, id: string, version: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* What to record about the packs being extracted.
|
|
37
|
+
*
|
|
38
|
+
* Exported so the pair is one fact: {@link foreignItemCatalogDirs} reads what
|
|
39
|
+
* this writes, and a test that hand-wrote the file would prove the reader
|
|
40
|
+
* against a transcription of the format rather than against the format.
|
|
41
|
+
*
|
|
42
|
+
* A pack declaring no `system` records `null` — Foundry requires the field on
|
|
43
|
+
* an Item pack, so this is the shape of a manifest that is wrong rather than a
|
|
44
|
+
* case with a meaning, and `null` reads as "neutral", which is the safe way to
|
|
45
|
+
* be wrong: a neutral pack is read by every system rather than by none.
|
|
46
|
+
*
|
|
47
|
+
* @param {readonly object[]} itemPacks - The manifest's Item pack entries.
|
|
48
|
+
* @returns {Array<{name: string, system: string|null}>} What each one is.
|
|
49
|
+
*/
|
|
50
|
+
export function itemPackManifest(itemPacks: readonly object[]): Array<{
|
|
51
|
+
name: string;
|
|
52
|
+
system: string | null;
|
|
53
|
+
}>;
|
|
35
54
|
/**
|
|
36
55
|
* Write an unzipped archive's entries under `dest`.
|
|
37
56
|
*
|
|
@@ -159,10 +178,27 @@ export function fetchAllCatalogs(config: object): Promise<number>;
|
|
|
159
178
|
* Reads the cache only. A cold cache is an error naming the command that fills
|
|
160
179
|
* it, rather than a download nobody asked for.
|
|
161
180
|
*
|
|
181
|
+
* **Scoped to one system when the caller compiles for one (#58)**, exactly as
|
|
182
|
+
* {@link module:engine/generate.itemPackJsonDirs} scopes the local half. The
|
|
183
|
+
* two halves answer the same lookup — `loadItemsMap` merges them into one
|
|
184
|
+
* address space keyed by `subType:shortcode` — so scoping only the local one
|
|
185
|
+
* leaves the collision it was meant to remove: `skill:awar` is a real address
|
|
186
|
+
* in both vocabularies, and a `harn-ensemble` actor compiled for `hm3` would
|
|
187
|
+
* resolve three quarters of its references against whichever document the
|
|
188
|
+
* dependency's `sohl` pack happened to supply. A pack that declares no system
|
|
189
|
+
* is neutral and always read; asking for no system reads every pack, which is
|
|
190
|
+
* every single-system build.
|
|
191
|
+
*
|
|
162
192
|
* @param {object} config - The resolved build configuration.
|
|
163
|
-
* @
|
|
193
|
+
* @param {string|null} [system] - The system the caller is compiling for.
|
|
194
|
+
* Omitted or `null`, every cached pack is read.
|
|
195
|
+
* @returns {Array<{dir: string, package: string}>} Every cached dependency's
|
|
196
|
+
* item directories, each with the package that published it.
|
|
164
197
|
*/
|
|
165
|
-
export function foreignItemCatalogDirs(config: object):
|
|
198
|
+
export function foreignItemCatalogDirs(config: object, system?: string | null): Array<{
|
|
199
|
+
dir: string;
|
|
200
|
+
package: string;
|
|
201
|
+
}>;
|
|
166
202
|
/**
|
|
167
203
|
* The file a system publishes its `system` field sets as (#60).
|
|
168
204
|
*
|
|
@@ -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
|
*
|
|
@@ -32,9 +126,11 @@ export function matchesKind(value: unknown, kind: string): boolean;
|
|
|
32
126
|
* Supplied by the caller like `schemas`, so this module states no list of
|
|
33
127
|
* iconless types of its own; absent it, an inert `img:` goes unreported
|
|
34
128
|
* rather than every note's being (#349).
|
|
35
|
-
* @param {Readonly<Record<string,
|
|
36
|
-
* The system blocks to check, and what each accepts.
|
|
37
|
-
*
|
|
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.
|
|
38
134
|
* @param {readonly string[]} [opts.packs] - The pack names this package
|
|
39
135
|
* declares, for a `data:` field whose map is keyed by pack. Supplied by the
|
|
40
136
|
* caller like `schemas` and `vocabulary`, and absent it no claim is made
|
|
@@ -49,10 +145,7 @@ export function lintNote(note: object, { schemas, index, vocabulary, packs, emit
|
|
|
49
145
|
document: string | null;
|
|
50
146
|
art: readonly string[];
|
|
51
147
|
} | null) | undefined;
|
|
52
|
-
systems?: Readonly<Record<string,
|
|
53
|
-
known?: readonly string[];
|
|
54
|
-
fieldVocabulary?: boolean;
|
|
55
|
-
}>> | undefined;
|
|
148
|
+
systems?: Readonly<Record<string, SystemBlockSpec>> | undefined;
|
|
56
149
|
packs?: readonly string[] | undefined;
|
|
57
150
|
}): object[];
|
|
58
151
|
/**
|
|
@@ -64,8 +157,9 @@ export function lintNote(note: object, { schemas, index, vocabulary, packs, emit
|
|
|
64
157
|
* @param {Record<string, object>} [opts.vocabulary] - Type → the closed regions
|
|
65
158
|
* it declares (#128); see {@link lintNote}.
|
|
66
159
|
* @param {boolean} [opts.references=true] - Whether to check references.
|
|
67
|
-
* @param {Readonly<Record<string,
|
|
68
|
-
* 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}.
|
|
69
163
|
* @param {readonly string[]} [opts.packs] - The declared pack names; see
|
|
70
164
|
* {@link lintNote}.
|
|
71
165
|
* @param {(type: string) => {document: string|null, art: readonly string[]}|null} [opts.emittedArt]
|
|
@@ -77,10 +171,7 @@ export function lintFrontmatter(index: object, { schemas, vocabulary, packs, emi
|
|
|
77
171
|
schemas: Record<string, readonly object[]>;
|
|
78
172
|
vocabulary?: Record<string, object> | undefined;
|
|
79
173
|
references?: boolean | undefined;
|
|
80
|
-
systems?: Readonly<Record<string,
|
|
81
|
-
known?: readonly string[];
|
|
82
|
-
fieldVocabulary?: boolean;
|
|
83
|
-
}>> | undefined;
|
|
174
|
+
systems?: Readonly<Record<string, SystemBlockSpec>> | undefined;
|
|
84
175
|
packs?: readonly string[] | undefined;
|
|
85
176
|
emittedArt?: ((type: string) => {
|
|
86
177
|
document: string | null;
|
|
@@ -123,21 +214,48 @@ export const UNIVERSAL_KEYS: ReadonlySet<string>;
|
|
|
123
214
|
* The system blocks a build checks, and what each accepts beyond the shared
|
|
124
215
|
* vocabulary.
|
|
125
216
|
*
|
|
126
|
-
* One entry, because one system is what every existing tree declares — and
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* 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.
|
|
132
222
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* `itemBuilders` registry that this system declares, and a second system's
|
|
136
|
-
* 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.
|
|
137
225
|
*
|
|
138
|
-
*
|
|
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>>}
|
|
139
239
|
*/
|
|
140
|
-
export const DEFAULT_SYSTEM_BLOCKS: Readonly<Record<string,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
};
|
|
@@ -462,6 +462,14 @@ export function expandNoteTables(body: string, { docs, name, fm, bodyLine, sqlTa
|
|
|
462
462
|
* underscores.
|
|
463
463
|
*/
|
|
464
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
|
+
*/
|
|
465
473
|
export const md: import("markdown-it").MarkdownIt;
|
|
466
474
|
export { slugify } from "./content-slug.mjs";
|
|
467
475
|
export { makeId } from "./ids.mjs";
|
package/types/engine/index.d.mts
CHANGED
|
@@ -25,6 +25,8 @@ export * as foundryEntries from "./foundry-entries.mjs";
|
|
|
25
25
|
export * as contentIndex from "./content-index.mjs";
|
|
26
26
|
export * as siteBuild from "./site-build.mjs";
|
|
27
27
|
export * as contentLint from "./content-lint.mjs";
|
|
28
|
+
export * as contentCharset from "./content-charset.mjs";
|
|
29
|
+
export * as contentIcons from "./content-icons.mjs";
|
|
28
30
|
export * as contentLinks from "./content-links.mjs";
|
|
29
31
|
export * as webWikilinks from "./web-wikilinks.mjs";
|
|
30
32
|
export * as contentTables from "./content-tables.mjs";
|
|
@@ -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
|
*
|