@heroiclands/package-build 8.1.0 → 10.0.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 +748 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +479 -123
- package/bin/package-build.mjs +27 -69
- package/bin/report.mjs +1 -2
- package/bundle.mjs +2 -10
- package/config.mjs +31 -106
- package/container.mjs +13 -57
- package/content-config.mjs +300 -188
- package/coverage.mjs +14 -55
- package/deploy.mjs +4 -13
- package/docs/content-format.md +1418 -0
- package/e2e.mjs +16 -55
- package/engine/address-charset.mjs +62 -0
- package/engine/address-diff.mjs +1 -4
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +203 -31
- package/engine/code-fences.mjs +4 -13
- package/engine/compendiums.mjs +13 -37
- package/engine/content-address.mjs +6 -10
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +144 -99
- package/engine/content-lint.mjs +12 -16
- package/engine/content-slug.mjs +2 -6
- package/engine/content-tables.mjs +26 -79
- package/engine/diagnostics.mjs +37 -15
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-reference.mjs +6 -20
- package/engine/field-spec.mjs +49 -45
- package/engine/foreign-catalog.mjs +7 -22
- package/engine/foreign-manifests.mjs +1 -4
- package/engine/frontmatter-lint.mjs +347 -43
- package/engine/frontmatter.mjs +3 -8
- package/engine/generate.mjs +36 -20
- package/engine/helpers.mjs +54 -81
- package/engine/homepage.mjs +4 -15
- package/engine/ids.mjs +21 -12
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -9
- package/engine/journals.mjs +4 -14
- package/engine/kb-manifest.mjs +41 -24
- package/engine/macros.mjs +2 -10
- package/engine/manifest-emit.mjs +6 -17
- package/engine/map-notes.mjs +53 -87
- package/engine/note-claims.mjs +383 -0
- package/engine/note-package.mjs +1 -4
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +56 -60
- package/engine/pack-router.mjs +18 -8
- package/engine/prose-config.mjs +20 -4
- package/engine/prose-lint.mjs +61 -17
- package/engine/region-events.mjs +1 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scene-levels.mjs +8 -22
- package/engine/scenes.mjs +31 -47
- package/engine/schema-check.mjs +348 -7
- package/engine/schema-extract.mjs +11 -39
- package/engine/site-build.mjs +13 -38
- package/engine/site-index.mjs +40 -35
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +115 -92
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +147 -183
- package/index.mjs +1 -5
- package/lang.mjs +1 -3
- package/manifest.mjs +10 -37
- package/markdownlint-config.mjs +1 -5
- package/package.json +6 -2
- package/sohl/actors.mjs +251 -68
- package/sohl/being-info.mjs +3 -6
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +4 -6
- package/sohl/item-builders.mjs +1 -3
- package/sohl/item-fields.mjs +16 -34
- package/sohl/items.mjs +111 -17
- package/sohl/kb-passes.mjs +29 -39
- package/sohl/note-schemas.mjs +11 -7
- package/sohl/skill-base.mjs +7 -23
- package/stage.mjs +3 -13
- package/templates.mjs +4 -15
- package/types/bundle.d.mts +1 -1
- package/types/container.d.mts +2 -2
- package/types/content-config.d.mts +48 -4
- package/types/coverage.d.mts +1 -1
- package/types/e2e.d.mts +4 -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 +15 -2
- package/types/engine/helpers.d.mts +22 -14
- 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/schema-extract.d.mts +1 -1
- package/types/engine/site-index.d.mts +1 -1
- 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/manifest.d.mts +1 -1
- 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,383 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **Which note types a configuration compiles at all** — and the finding for a
|
|
16
|
+
* note whose type nothing claims (#146).
|
|
17
|
+
*
|
|
18
|
+
* Every compile pass answers one question about a note: _is this mine?_ A note
|
|
19
|
+
* every pass answers "no" to is skipped as quietly as the thousands that
|
|
20
|
+
* legitimately belong to another pass — and where **no** pass would have said
|
|
21
|
+
* yes, that quiet is the whole of the report. `harn-ensemble` declares no
|
|
22
|
+
* `itemBuilders`, so its five `affiliation` notes were claimed by nothing: the
|
|
23
|
+
* journals pass rejected them, the Actor passes rejected them, and no Item pack
|
|
24
|
+
* existed to claim them. They vanished from the build with no error, no warning
|
|
25
|
+
* and no census line, while its 2,512 `being` notes each produced a routing
|
|
26
|
+
* error — the same failure with more information, because some pass got far
|
|
27
|
+
* enough to complain.
|
|
28
|
+
*
|
|
29
|
+
* This module supplies the missing complaint, and it is asked **once per
|
|
30
|
+
* build** rather than once per pass. That is not an optimisation: it is the
|
|
31
|
+
* only place the question can be answered correctly. #79's rule is that a
|
|
32
|
+
* markdown type with no mapping in a given system produces no document *for
|
|
33
|
+
* that system*, silently and correctly — so a per-pass check would report
|
|
34
|
+
* `armorlocation` against every system that does not map it, which is precisely
|
|
35
|
+
* the noise the rule forbids. "No system claims it at all" is a different
|
|
36
|
+
* statement, and only the whole configuration can make it.
|
|
37
|
+
*
|
|
38
|
+
* ## The two conditions, and why they are not one
|
|
39
|
+
*
|
|
40
|
+
* | condition | what it means | whose fix |
|
|
41
|
+
* | --- | --- | --- |
|
|
42
|
+
* | in the **vocabulary**, claimed by no pack | this build knows the type; nothing here is configured to compile it | configuration |
|
|
43
|
+
* | not in the vocabulary | nothing anywhere knows the type | authoring |
|
|
44
|
+
*
|
|
45
|
+
* The **vocabulary** is deliberately wider than one repository's
|
|
46
|
+
* configuration: it is what this toolchain and the systems it ships know a note
|
|
47
|
+
* type to be. `affiliation` is a SoHL Item however a given repository is
|
|
48
|
+
* configured, so a tree full of `affiliation` notes with no Item pack behind
|
|
49
|
+
* them is a repository that has not finished configuring itself — not an author
|
|
50
|
+
* who invented a word. Collapsing the two would send `harn-ensemble` to correct
|
|
51
|
+
* five perfectly good notes.
|
|
52
|
+
*
|
|
53
|
+
* ## The claim table mirrors `selects`, and a test holds them together
|
|
54
|
+
*
|
|
55
|
+
* Which note types a pass claims is stated by that pass's `selects`, and the
|
|
56
|
+
* table below is a second statement of the same fact — the only form in which
|
|
57
|
+
* the question can be asked of a pack that is *not* configured, which is exactly
|
|
58
|
+
* the question here. `tests/unclaimed-note-types.test.ts` compares the two for
|
|
59
|
+
* every type in the vocabulary, so the two statements cannot drift apart.
|
|
60
|
+
*
|
|
61
|
+
* @module
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
import { walkMarkdownTree } from "./helpers.mjs";
|
|
65
|
+
import { MAP_TYPES, PACK_BY_TYPE, RETIRED_TYPES } from "./ids.mjs";
|
|
66
|
+
import { itemTypes } from "./item-registry.mjs";
|
|
67
|
+
import { docEntryTypes } from "./item-docs.mjs";
|
|
68
|
+
import { loadPackConfig } from "./pack-config.mjs";
|
|
69
|
+
import { locateFrontmatterKey } from "./retired-fields.mjs";
|
|
70
|
+
import { noteTypesFor, subtypeRow } from "./document-subtypes.mjs";
|
|
71
|
+
import { HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
72
|
+
import { SOHL_DOCUMENT_SUBTYPES } from "../sohl/document-subtypes.mjs";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Note types that compile into **no compendium document, by design**.
|
|
76
|
+
*
|
|
77
|
+
* A homepage compiles into a *page*: it carries no compendium UUID, appears in
|
|
78
|
+
* no pack and in no link-manifest entry, and every package that publishes one
|
|
79
|
+
* would otherwise be told its front page is unclaimed. It is the one type whose
|
|
80
|
+
* absence from every pack is the intended state rather than a gap.
|
|
81
|
+
*
|
|
82
|
+
* @type {ReadonlySet<string>}
|
|
83
|
+
*/
|
|
84
|
+
export const NEVER_PACKED_TYPES = Object.freeze(new Set([HOMEPAGE_TYPE]));
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The note-type → document-subtype maps this toolchain ships.
|
|
88
|
+
*
|
|
89
|
+
* One today. `hm3/` is #139, and when it lands its map joins this list rather
|
|
90
|
+
* than the claim table below growing a second copy of the same fact.
|
|
91
|
+
*
|
|
92
|
+
* `engine/` importing from `sohl/` is the arrangement `generate.mjs` already
|
|
93
|
+
* has — its `COMPILERS` table names the SoHL compilers by class — and for the
|
|
94
|
+
* same reason: the engine owns the *mechanism* that asks each system what it
|
|
95
|
+
* compiles, and the systems own the answers.
|
|
96
|
+
*
|
|
97
|
+
* @type {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]}
|
|
98
|
+
*/
|
|
99
|
+
export const KNOWN_DOCUMENT_SUBTYPE_MAPS = Object.freeze([SOHL_DOCUMENT_SUBTYPES]);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* What a claim question is asked against.
|
|
103
|
+
*
|
|
104
|
+
* Each field defaults to what the compilers themselves read, so the answer here
|
|
105
|
+
* and the answer a pass gives are drawn from one source. They are parameters so
|
|
106
|
+
* that a test can pose a configuration this toolchain does not ship — two
|
|
107
|
+
* systems cutting the vocabulary differently, a registry declaring nothing —
|
|
108
|
+
* without a content tree or a config file on disk.
|
|
109
|
+
*
|
|
110
|
+
* @typedef {object} ClaimSources
|
|
111
|
+
* @property {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]} [maps] -
|
|
112
|
+
* The systems' note-type → document-subtype maps.
|
|
113
|
+
* @property {ReadonlySet<string>} [itemTypes] - The declared item vocabulary.
|
|
114
|
+
* @property {ReadonlySet<string>} [docEntryTypes] - The doc-carrying types.
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Fill in whatever a caller did not supply, from the configured registries.
|
|
119
|
+
*
|
|
120
|
+
* @param {ClaimSources} [sources] - What the caller supplied.
|
|
121
|
+
* @returns {Required<ClaimSources>} Every source, resolved.
|
|
122
|
+
*/
|
|
123
|
+
function resolveSources(sources = {}) {
|
|
124
|
+
return {
|
|
125
|
+
maps: sources.maps ?? KNOWN_DOCUMENT_SUBTYPE_MAPS,
|
|
126
|
+
itemTypes: sources.itemTypes ?? itemTypes(),
|
|
127
|
+
docEntryTypes: sources.docEntryTypes ?? docEntryTypes(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The Foundry document classes the systems say a note type compiles into.
|
|
133
|
+
*
|
|
134
|
+
* @param {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]} maps -
|
|
135
|
+
* The systems' maps.
|
|
136
|
+
* @param {string} type - The note's declared `type`.
|
|
137
|
+
* @returns {string[]} The document classes, deduplicated, in map order.
|
|
138
|
+
*/
|
|
139
|
+
function mappedDocuments(maps, type) {
|
|
140
|
+
const documents = [];
|
|
141
|
+
for (const map of maps) {
|
|
142
|
+
const row = subtypeRow(map, type);
|
|
143
|
+
if (row && !documents.includes(row.document)) documents.push(row.document);
|
|
144
|
+
}
|
|
145
|
+
return documents;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The systems whose map names a note type.
|
|
150
|
+
*
|
|
151
|
+
* @param {readonly import("./document-subtypes.mjs").DocumentSubtypeMap[]} maps -
|
|
152
|
+
* The systems' maps.
|
|
153
|
+
* @param {string} type - The note's declared `type`.
|
|
154
|
+
* @returns {string[]} The system ids, in map order.
|
|
155
|
+
*/
|
|
156
|
+
function mappingSystems(maps, type) {
|
|
157
|
+
return maps.filter((map) => subtypeRow(map, type)).map((map) => map.system);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The note types a pass of one document type claims — the claim table.
|
|
162
|
+
*
|
|
163
|
+
* Each row restates one pass's `selects`, in the only form that can be asked of
|
|
164
|
+
* a pack the configuration does not declare. A document type no compiler is
|
|
165
|
+
* registered for claims nothing, which is what keeps a prebuilt `Adventure`
|
|
166
|
+
* pack from appearing to answer for any note.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} docType - The Foundry document type a pack holds.
|
|
169
|
+
* @param {ClaimSources} [sources] - What to answer from. Defaults to the
|
|
170
|
+
* configured registries and the systems this toolchain ships.
|
|
171
|
+
* @returns {ReadonlySet<string>} The note types such a pass would claim.
|
|
172
|
+
*/
|
|
173
|
+
export function noteTypesClaimedBy(docType, sources) {
|
|
174
|
+
const { maps, itemTypes: items, docEntryTypes: docs } = resolveSources(sources);
|
|
175
|
+
switch (docType) {
|
|
176
|
+
// The items pass: the declared registry's keys, filtered by the
|
|
177
|
+
// systems' maps — a type a system sends to some *other* document class
|
|
178
|
+
// is not an item however a registry spells it.
|
|
179
|
+
case "Item":
|
|
180
|
+
return Object.freeze(
|
|
181
|
+
new Set(
|
|
182
|
+
[...items].filter((type) => {
|
|
183
|
+
const documents = mappedDocuments(maps, type);
|
|
184
|
+
return documents.length === 0 || documents.includes("Item");
|
|
185
|
+
}),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
// The actors pass: every note type a system sends to an `Actor`.
|
|
189
|
+
case "Actor":
|
|
190
|
+
return Object.freeze(new Set(maps.flatMap((map) => noteTypesFor(map, "Actor"))));
|
|
191
|
+
// The journals pass: free prose, plus every doc-carrying type — an
|
|
192
|
+
// item's, a macro's and a map note's description each compile into a
|
|
193
|
+
// JournalEntry of their own.
|
|
194
|
+
case "JournalEntry":
|
|
195
|
+
return Object.freeze(new Set(["doc", ...docs]));
|
|
196
|
+
case "Macro":
|
|
197
|
+
return Object.freeze(new Set(["macro"]));
|
|
198
|
+
case "Scene":
|
|
199
|
+
return Object.freeze(new Set(MAP_TYPES));
|
|
200
|
+
default:
|
|
201
|
+
return Object.freeze(new Set());
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Every note type some pack in a configuration would compile.
|
|
207
|
+
*
|
|
208
|
+
* The union across the configured pack list, so a type claimed by any one pack
|
|
209
|
+
* is claimed — which is what keeps a type deliberately unmapped for one system,
|
|
210
|
+
* and claimed for another, silent (#79).
|
|
211
|
+
*
|
|
212
|
+
* @param {object} [config] - The resolved build configuration. Defaults to this
|
|
213
|
+
* repository's.
|
|
214
|
+
* @param {ClaimSources} [sources] - What to answer from.
|
|
215
|
+
* @returns {ReadonlySet<string>} The claimed note types.
|
|
216
|
+
*/
|
|
217
|
+
export function claimedNoteTypes(config = loadPackConfig(), sources) {
|
|
218
|
+
const claimed = new Set();
|
|
219
|
+
for (const pack of config.packs ?? []) {
|
|
220
|
+
for (const type of noteTypesClaimedBy(pack.type, sources)) claimed.add(type);
|
|
221
|
+
}
|
|
222
|
+
return Object.freeze(claimed);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Every note type this build knows, whatever any one repository configures.
|
|
227
|
+
*
|
|
228
|
+
* Wider than {@link claimedNoteTypes} on purpose: it is what distinguishes a
|
|
229
|
+
* repository that has not configured a pack for a real content type from an
|
|
230
|
+
* author who wrote a word nothing anywhere compiles. The engine's own types,
|
|
231
|
+
* the types every shipped system maps, and whatever the configured registries
|
|
232
|
+
* declare on top.
|
|
233
|
+
*
|
|
234
|
+
* @param {ClaimSources} [sources] - What to answer from.
|
|
235
|
+
* @returns {ReadonlySet<string>} The vocabulary.
|
|
236
|
+
*/
|
|
237
|
+
export function noteTypeVocabulary(sources) {
|
|
238
|
+
const { maps, itemTypes: items } = resolveSources(sources);
|
|
239
|
+
return Object.freeze(
|
|
240
|
+
new Set([
|
|
241
|
+
...Object.keys(PACK_BY_TYPE),
|
|
242
|
+
...MAP_TYPES,
|
|
243
|
+
...NEVER_PACKED_TYPES,
|
|
244
|
+
...items,
|
|
245
|
+
...maps.flatMap((map) => Object.keys(map.types)),
|
|
246
|
+
]),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* A readable list — `"a", "b" or "c"`.
|
|
252
|
+
*
|
|
253
|
+
* @param {readonly string[]} values - The values.
|
|
254
|
+
* @returns {string} The list.
|
|
255
|
+
*/
|
|
256
|
+
function list(values) {
|
|
257
|
+
const quoted = values.map((value) => `"${value}"`);
|
|
258
|
+
if (quoted.length <= 1) return quoted.join("");
|
|
259
|
+
return `${quoted.slice(0, -1).join(", ")} or ${quoted[quoted.length - 1]}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* `"a"` or `"an"`, so a document class reads as English wherever it is named.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} word - The word the article precedes.
|
|
266
|
+
* @returns {string} The article.
|
|
267
|
+
*/
|
|
268
|
+
function article(word) {
|
|
269
|
+
return /^[AEIOUaeiou]/.test(word) ? "an" : "a";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The **configuration** finding: this build knows the type, and nothing here
|
|
274
|
+
* compiles it.
|
|
275
|
+
*
|
|
276
|
+
* The remedy names what is actually missing rather than a generic one, because
|
|
277
|
+
* the two halves fail independently: `harn-ensemble` has neither the registry
|
|
278
|
+
* entry nor the pack, and a repository that declares a registry and forgets the
|
|
279
|
+
* pack has only the second.
|
|
280
|
+
*
|
|
281
|
+
* @param {string} type - The note's declared `type`.
|
|
282
|
+
* @param {object} config - The resolved build configuration.
|
|
283
|
+
* @param {Required<ClaimSources>} sources - What to answer from.
|
|
284
|
+
* @returns {string} The message.
|
|
285
|
+
*/
|
|
286
|
+
function configurationMessage(type, config, sources) {
|
|
287
|
+
const documents = mappedDocuments(sources.maps, type);
|
|
288
|
+
if (!documents.length && sources.itemTypes.has(type)) documents.push("Item");
|
|
289
|
+
if (!documents.length && PACK_BY_TYPE[type]) documents.push(PACK_BY_TYPE[type].docType);
|
|
290
|
+
|
|
291
|
+
const systems = mappingSystems(sources.maps, type);
|
|
292
|
+
const configured = new Set((config.packs ?? []).map((pack) => pack.type));
|
|
293
|
+
const packless = documents.filter((document) => !configured.has(document));
|
|
294
|
+
const needsBuilder = documents.includes("Item") && !sources.itemTypes.has(type);
|
|
295
|
+
|
|
296
|
+
const into = documents.map((document) => `${article(document)} ${document}`).join(" or ");
|
|
297
|
+
const becomes =
|
|
298
|
+
documents.length ?
|
|
299
|
+
systems.length ?
|
|
300
|
+
`The ${list(systems)} system${systems.length > 1 ? "s" : ""} ` +
|
|
301
|
+
`compile${systems.length > 1 ? "" : "s"} it into ${into}`
|
|
302
|
+
: `It compiles into ${into}`
|
|
303
|
+
: `Nothing configured here compiles it`;
|
|
304
|
+
|
|
305
|
+
const remedy =
|
|
306
|
+
packless.length && needsBuilder ?
|
|
307
|
+
`\`packs:\` declares no ${packless.join(" or ")} pack and no ` +
|
|
308
|
+
`\`itemBuilders\` registry declares "${type}" — declare both in ` +
|
|
309
|
+
`package-build.config.yaml`
|
|
310
|
+
: packless.length ?
|
|
311
|
+
`\`packs:\` declares no ${packless.join(" or ")} pack — declare one in ` +
|
|
312
|
+
`package-build.config.yaml`
|
|
313
|
+
: needsBuilder ?
|
|
314
|
+
`no \`itemBuilders\` registry declares "${type}" — declare it in ` +
|
|
315
|
+
`package-build.config.yaml`
|
|
316
|
+
: `nothing in \`packs:\` claims it — check the pack list in ` +
|
|
317
|
+
`package-build.config.yaml`;
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
`no configured pack claims a note of type "${type}", so it compiles ` +
|
|
321
|
+
`into nothing. ${becomes}, but ${remedy}, or stop authoring the type.`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The **authoring** finding: nothing anywhere knows the type.
|
|
327
|
+
*
|
|
328
|
+
* @param {string} type - The note's declared `type`.
|
|
329
|
+
* @returns {string} The message.
|
|
330
|
+
*/
|
|
331
|
+
function authoringMessage(type) {
|
|
332
|
+
return (
|
|
333
|
+
`no configured pack claims a note of type "${type}", so it compiles ` +
|
|
334
|
+
`into nothing — and "${type}" is not a content type this build knows ` +
|
|
335
|
+
`at all: no system maps it and no \`itemBuilders\` registry declares it. ` +
|
|
336
|
+
`Correct the note's \`type:\`, or declare the type alongside the ones ` +
|
|
337
|
+
`this repository already ships.`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Every note in the content tree that no configured pack would compile.
|
|
343
|
+
*
|
|
344
|
+
* Read-only: it walks the tree and reports, and writes nothing. Three kinds of
|
|
345
|
+
* note are passed over, each for a stated reason rather than by omission — a
|
|
346
|
+
* file with no frontmatter is not a note; a note with no `type:` is the
|
|
347
|
+
* frontmatter linter's finding, which can say what a type is *for*; and a
|
|
348
|
+
* retired type is answered by `assertTypeNotRetired` in `ids.mjs`, which names the
|
|
349
|
+
* replacement.
|
|
350
|
+
*
|
|
351
|
+
* @param {object} [config] - The resolved build configuration. Defaults to this
|
|
352
|
+
* repository's.
|
|
353
|
+
* @param {ClaimSources} [sources] - What to answer from.
|
|
354
|
+
* @returns {Array<{file: string, line?: number, column?: number,
|
|
355
|
+
* severity: "error", message: string, type: string}>} One finding per note.
|
|
356
|
+
*/
|
|
357
|
+
export function unclaimedNoteFindings(config = loadPackConfig(), sources) {
|
|
358
|
+
const resolved = resolveSources(sources);
|
|
359
|
+
const claimed = claimedNoteTypes(config, resolved);
|
|
360
|
+
const vocabulary = noteTypeVocabulary(resolved);
|
|
361
|
+
const findings = [];
|
|
362
|
+
|
|
363
|
+
for (const { frontmatter: fm, absPath } of walkMarkdownTree(config.paths.content)) {
|
|
364
|
+
if (!fm) continue;
|
|
365
|
+
const type = typeof fm.type === "string" ? fm.type.trim() : "";
|
|
366
|
+
if (!type) continue;
|
|
367
|
+
if (NEVER_PACKED_TYPES.has(type)) continue;
|
|
368
|
+
if (RETIRED_TYPES[type]) continue;
|
|
369
|
+
if (claimed.has(type)) continue;
|
|
370
|
+
|
|
371
|
+
findings.push({
|
|
372
|
+
file: absPath,
|
|
373
|
+
...locateFrontmatterKey(absPath, "type", type),
|
|
374
|
+
severity: /** @type {"error"} */ ("error"),
|
|
375
|
+
type,
|
|
376
|
+
message:
|
|
377
|
+
vocabulary.has(type) ?
|
|
378
|
+
configurationMessage(type, config, resolved)
|
|
379
|
+
: authoringMessage(type),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return findings;
|
|
383
|
+
}
|
package/engine/note-package.mjs
CHANGED
|
@@ -104,10 +104,7 @@ export function searchableFrontmatter(fm, configured) {
|
|
|
104
104
|
* @returns {void}
|
|
105
105
|
* @throws {Error} When the note declares the field.
|
|
106
106
|
*/
|
|
107
|
-
export function assertNoDeclaredPackage(
|
|
108
|
-
fm,
|
|
109
|
-
{ file, absPath, configured } = {},
|
|
110
|
-
) {
|
|
107
|
+
export function assertNoDeclaredPackage(fm, { file, absPath, configured } = {}) {
|
|
111
108
|
if (!fm || typeof fm !== "object" || !Object.hasOwn(fm, "package")) return;
|
|
112
109
|
|
|
113
110
|
const target = configured ?? contentPackage();
|