@heroiclands/package-build 11.0.0 → 11.1.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 +96 -0
- package/CONTENT.md +16 -0
- package/bin/content-build.mjs +20 -0
- package/content-config.mjs +28 -0
- package/engine/frontmatter-lint.mjs +135 -5
- package/package.json +1 -1
- package/types/content-config.d.mts +24 -0
- package/types/engine/frontmatter-lint.d.mts +27 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 11.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8906d7a: **A `README` landing's `subType` is checked against the sections the repository declares, not the genre list.**
|
|
8
|
+
|
|
9
|
+
Two vocabularies were spelled with one key. For every note, `subType` is a
|
|
10
|
+
sub-kind of its type, checked against a closed list. For a `README` under
|
|
11
|
+
`publish.address.landing: readme` it is additionally the **URL section** —
|
|
12
|
+
`sectionOf` reads a `doc`'s `subType` as the segment the landing addresses — and
|
|
13
|
+
that is an open set the consuming repository names in `site.sections` /
|
|
14
|
+
`site.readmeSections`. `engine/frontmatter-lint.mjs` applied the closed reading
|
|
15
|
+
while `engine/content-address.mjs` applied the open one, so the two halves of the
|
|
16
|
+
toolchain disagreed about the same note: `content-build site` addressed and
|
|
17
|
+
published `Weapons/README.md` at `weapongear/`, and `content-build lint` refused
|
|
18
|
+
it because `doc` declares only `rules`, `user-guide` and `reference`. An item
|
|
19
|
+
section's landing was not expressible.
|
|
20
|
+
|
|
21
|
+
The check now widens **for a landing only**, and only under `landing: readme`: a
|
|
22
|
+
`README` whose `subType` is its section may name any section the repository
|
|
23
|
+
declares, as well as any of its type's own genres — a genre is a section a `doc`
|
|
24
|
+
tree publishes under whether or not the repository describes it, so narrowing to
|
|
25
|
+
the configured set alone would refuse a correct `README`. An ordinary note's
|
|
26
|
+
`subType` stays closed to its type's genres, unchanged.
|
|
27
|
+
|
|
28
|
+
**The guard survives**, checked against the set that actually decides where the
|
|
29
|
+
page goes:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Weapons/README.md:4:1: error: `subType` "weapongeer" is the section this README
|
|
33
|
+
lands at, and nothing declares it: it is neither a section this repository
|
|
34
|
+
configures under `site.sections` / `site.readmeSections` (rules, user-guide,
|
|
35
|
+
weapongear, …) nor one of the subtypes doc declares (rules, user-guide,
|
|
36
|
+
reference). Did you mean "weapongear"?
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Whether the value is an address is asked of `sectionOf` rather than by naming a
|
|
40
|
+
type, so the linter still knows no type names of its own; the section list is
|
|
41
|
+
read from the resolved configuration the site build renders those landings from,
|
|
42
|
+
through a new `declaredSections(config)`, so neither can name a section the other
|
|
43
|
+
does not.
|
|
44
|
+
|
|
45
|
+
_Minor rather than patch_: `lintNote` and `lintFrontmatter` take two new
|
|
46
|
+
options (`landing`, `sections`) and `content-config.mjs` exports
|
|
47
|
+
`declaredSections`. Both options default to today's behaviour, so a caller that
|
|
48
|
+
passes neither — and a repository that declares no sections — is unaffected.
|
|
49
|
+
|
|
50
|
+
Closes #197
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- 68910b6: **A `README` landing may name any section that exists, not only a configured one.**
|
|
55
|
+
|
|
56
|
+
#197 widened a landing's `subType` check by one term and it was the wrong one:
|
|
57
|
+
the sections the repository configures in `site.sections` /
|
|
58
|
+
`site.readmeSections`. That keys on configuration a consumer may legitimately not
|
|
59
|
+
have. `sohl-thalorna` has **no `site:` block at all** — it renders its site
|
|
60
|
+
through a local fork of the emitter — so `declaredSections` answered `[]`, the
|
|
61
|
+
accepted set collapsed back to the three `doc` genres, and five of its landings
|
|
62
|
+
were still refused for naming their own content type:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
assets/content/Characters/README.md:7:1: error: `subType` "being" is not one of the subtypes doc declares (rules, user-guide, reference)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A landing's `subType` is an **address**, so it is now checked against the
|
|
69
|
+
addresses that exist — the range of `sectionOf` over every note the format
|
|
70
|
+
permits, plus whatever the repository names:
|
|
71
|
+
|
|
72
|
+
1. **Every content type the specification declares.** `sectionOf` returns
|
|
73
|
+
`fm.type` for a non-`doc` note, so `being`, `lore`, `scenario` and
|
|
74
|
+
`weapongear` are sections _by construction_, configured or not.
|
|
75
|
+
2. **The type's own subtypes** — `rules`, `user-guide`, `reference`.
|
|
76
|
+
3. **The configured sections**, which may name one that is neither: `sohl` has
|
|
77
|
+
`credits` and `dev-docs`.
|
|
78
|
+
|
|
79
|
+
**The guard survives.** A misspelling is in none of the three, so it still fails,
|
|
80
|
+
and the near miss is drawn from the whole union. What is deliberately no longer
|
|
81
|
+
caught is a landing for a section that exists but is currently empty — which is
|
|
82
|
+
legitimate, and is why the set is the types the format _declares_ rather than
|
|
83
|
+
those _present in the tree_: `sohl` ships two such landings above tables that
|
|
84
|
+
stay empty until the first note of each type does.
|
|
85
|
+
|
|
86
|
+
**The type list is the specification's, not the schema map's.** `NOTE_SCHEMAS`
|
|
87
|
+
declares 18 types and `docs/content-format.md` declares 23; `lore`, `place`,
|
|
88
|
+
`scenario`, `vehicle` and `armorlocation` are in the second only. Checking an
|
|
89
|
+
address against the schema map would refuse `Lore/README.md` for a reason that
|
|
90
|
+
is not about addresses, and would report one gap twice in two vocabularies.
|
|
91
|
+
Whether a note's fields can be checked is a different question, answered
|
|
92
|
+
elsewhere and reported on the notes themselves.
|
|
93
|
+
|
|
94
|
+
`lintNote` / `lintFrontmatter` take a `types` option beside `sections`; both
|
|
95
|
+
default to empty, so a caller that passes neither is unaffected.
|
|
96
|
+
|
|
97
|
+
Closes #200
|
|
98
|
+
|
|
3
99
|
## 11.0.0
|
|
4
100
|
|
|
5
101
|
### Major Changes
|
package/CONTENT.md
CHANGED
|
@@ -1252,6 +1252,22 @@ publish:
|
|
|
1252
1252
|
The two are alternatives rather than a pair that could both apply: each live
|
|
1253
1253
|
content tree holds notes the other rule would move.
|
|
1254
1254
|
|
|
1255
|
+
**Under `readme`, a landing's `subType` is an address rather than a genre.** The
|
|
1256
|
+
segment the `README` lands at is what `sectionOf` reads — for a `doc`, its
|
|
1257
|
+
`subType` — so `Weapons/README.md` writes `subType: weapongear` and publishes at
|
|
1258
|
+
`weapongear/`. `content-build lint` checks that value against the sections that
|
|
1259
|
+
can exist, which is three sets: every **content type** the format declares
|
|
1260
|
+
(`sectionOf` returns a non-`doc` note's own type, so `being` and `weapongear` are
|
|
1261
|
+
sections by construction), the **subtypes the type declares** (`rules`,
|
|
1262
|
+
`user-guide`, `reference`), and any section named in
|
|
1263
|
+
[`site.sections` / `site.readmeSections`](#what-a-section-may-declare). A
|
|
1264
|
+
misspelt one is still refused, by name and against all three, with the near miss
|
|
1265
|
+
suggested. This applies to a `README` only: every other note's `subType` stays
|
|
1266
|
+
closed to the values its type declares.
|
|
1267
|
+
|
|
1268
|
+
Configuring the section is **not** a prerequisite — `site.sections` is framing,
|
|
1269
|
+
and a package that renders its own site need declare no `site:` block at all.
|
|
1270
|
+
|
|
1255
1271
|
A note the scheme yields no address for — one carrying no `shortcode`, a `doc`
|
|
1256
1272
|
with no subtype (so no section to be filed under), a collection note naming no
|
|
1257
1273
|
section — is **reported and omitted**, never guessed. The command
|
package/bin/content-build.mjs
CHANGED
|
@@ -72,6 +72,10 @@ import {
|
|
|
72
72
|
import { renderItemFieldReference } from "../engine/field-reference.mjs";
|
|
73
73
|
import { lintContentTree } from "../engine/content-lint.mjs";
|
|
74
74
|
import { lintFrontmatter } from "../engine/frontmatter-lint.mjs";
|
|
75
|
+
// The sections this repository declares — the open set a `README` landing's
|
|
76
|
+
// `subType` names (#197). Read from the resolved configuration the site build
|
|
77
|
+
// renders those landings from, so neither can name a section the other does not.
|
|
78
|
+
import { declaredSections } from "../content-config.mjs";
|
|
75
79
|
import { loadContentFormat } from "../engine/content-format.mjs";
|
|
76
80
|
import {
|
|
77
81
|
checkDeclaredFields,
|
|
@@ -736,6 +740,22 @@ function lintCommand() {
|
|
|
736
740
|
// decides which vocabulary a tree is held to.
|
|
737
741
|
vocabulary: NOTE_VOCABULARY,
|
|
738
742
|
references: argv.references,
|
|
743
|
+
// How this repository addresses a section landing, and the
|
|
744
|
+
// sections that can exist — the open vocabulary a
|
|
745
|
+
// `README`'s `subType` is read against (#197, #200).
|
|
746
|
+
// Passed in for the same reason the schemas are: the
|
|
747
|
+
// linter checks a note against what it is handed.
|
|
748
|
+
//
|
|
749
|
+
// The types come from the **specification**, not from
|
|
750
|
+
// `schemas`: a landing's `subType` is an address, and a
|
|
751
|
+
// type the format declares is a section whether or not
|
|
752
|
+
// this build carries a schema to check its notes' fields
|
|
753
|
+
// with. Reading `schemas` here would refuse `Lore/README.md`
|
|
754
|
+
// for a reason that is not about addresses, and report one
|
|
755
|
+
// gap twice in two vocabularies.
|
|
756
|
+
landing: config.publish.address.landing,
|
|
757
|
+
types: [...loadContentFormat().types.keys()],
|
|
758
|
+
sections: declaredSections(config),
|
|
739
759
|
});
|
|
740
760
|
|
|
741
761
|
// What the builders emit, against what the receiving system
|
package/content-config.mjs
CHANGED
|
@@ -210,6 +210,34 @@ export function publishesContentPages(config) {
|
|
|
210
210
|
return config.publish.site === "content";
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Every URL section this repository names, in declaration order (#197).
|
|
215
|
+
*
|
|
216
|
+
* A section is *declared* by describing it: `site.sections` for one whose
|
|
217
|
+
* landing this build generates, `site.readmeSections` for one whose landing is
|
|
218
|
+
* a `README`. Between them they are the open set of addresses a repository
|
|
219
|
+
* says it publishes under — which is what a `README` landing's `subType` names,
|
|
220
|
+
* since `sectionOf` reads that field as the section rather than as a genre.
|
|
221
|
+
*
|
|
222
|
+
* Read from the same two maps the site build renders each landing from, so the
|
|
223
|
+
* set a note is checked against and the set a landing is written from cannot
|
|
224
|
+
* come to disagree. A repository that describes no section declares none, and
|
|
225
|
+
* the answer is empty rather than a guess assembled from the tree.
|
|
226
|
+
*
|
|
227
|
+
* @param {{site: {sections: object, readmeSections: object}}} config - A
|
|
228
|
+
* resolved configuration.
|
|
229
|
+
* @returns {readonly string[]} The section names, deduplicated.
|
|
230
|
+
*/
|
|
231
|
+
export function declaredSections(config) {
|
|
232
|
+
const site = config?.site ?? {};
|
|
233
|
+
return Object.freeze([
|
|
234
|
+
...new Set([
|
|
235
|
+
...Object.keys(site.sections ?? {}),
|
|
236
|
+
...Object.keys(site.readmeSections ?? {}),
|
|
237
|
+
]),
|
|
238
|
+
]);
|
|
239
|
+
}
|
|
240
|
+
|
|
213
241
|
/**
|
|
214
242
|
* @typedef {"systems" | "modules"} PackageKind
|
|
215
243
|
*/
|
|
@@ -55,7 +55,10 @@
|
|
|
55
55
|
* @module
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
|
+
import path from "node:path";
|
|
59
|
+
|
|
58
60
|
import { authoredFields } from "./field-spec.mjs";
|
|
61
|
+
import { DEFAULT_ADDRESS_SCHEME, sectionOf } from "./content-address.mjs";
|
|
59
62
|
import { resolveFieldValue, SYSTEM_BLOCK_KEYS, unknownBlockKeys } from "./system-block.mjs";
|
|
60
63
|
import { positionInFrontmatter, positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
61
64
|
import { checkHomepageAddressFields } from "./homepage.mjs";
|
|
@@ -313,22 +316,83 @@ function checkDataContainer(note, { type, fields }) {
|
|
|
313
316
|
return findings;
|
|
314
317
|
}
|
|
315
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Whether this note's `subType` names the **section it lands at** rather than a
|
|
321
|
+
* sub-kind of its type (#197).
|
|
322
|
+
*
|
|
323
|
+
* Under `landing: readme` a `README.md` addresses its whole section, and the
|
|
324
|
+
* segment is what `sectionOf` reads — for a `doc`, its `subType`. So that one
|
|
325
|
+
* field is spelled once and read twice: as a genre on an ordinary note, and as
|
|
326
|
+
* an address on a landing. The two vocabularies are not the same set, and the
|
|
327
|
+
* closed one cannot answer for the open one.
|
|
328
|
+
*
|
|
329
|
+
* Asked of {@link sectionOf} rather than by naming a type, because which types
|
|
330
|
+
* route by their `subType` is address knowledge and this module deliberately
|
|
331
|
+
* knows no type names of its own.
|
|
332
|
+
*
|
|
333
|
+
* @param {object} note - The note.
|
|
334
|
+
* @param {string} landing - The repository's landing rule.
|
|
335
|
+
* @returns {boolean} Whether the value is an address.
|
|
336
|
+
*/
|
|
337
|
+
function subTypeIsSection(note, landing) {
|
|
338
|
+
if (landing !== "readme") return false;
|
|
339
|
+
const file = typeof note.file === "string" ? note.file : "";
|
|
340
|
+
if (path.basename(file).toLowerCase() !== "readme.md") return false;
|
|
341
|
+
const fm = note.fm ?? {};
|
|
342
|
+
return typeof fm.subType === "string" && sectionOf(fm) === fm.subType;
|
|
343
|
+
}
|
|
344
|
+
|
|
316
345
|
/**
|
|
317
346
|
* Check a note's top-level `subType` against the values its type declares
|
|
318
|
-
* (#128)
|
|
347
|
+
* (#128), or — for a `README` landing — against the sections that can exist
|
|
348
|
+
* (#197, #200).
|
|
319
349
|
*
|
|
320
350
|
* `subType` stays at the top level — it is what each system's map reads to
|
|
321
351
|
* derive a document type, so it describes the note rather than the subject —
|
|
322
352
|
* but it is not open like the rest of that region: a type either declares a
|
|
323
353
|
* `subType` or does not, and a type that does declares its values.
|
|
324
354
|
*
|
|
355
|
+
* **A landing page's is an address, and an address is checked against the
|
|
356
|
+
* addresses that exist.** A `README` under `landing: readme` addresses its
|
|
357
|
+
* section through this field, and a section is `weapongear` or `being` as
|
|
358
|
+
* readily as it is `rules`. What can legitimately appear there is the range of
|
|
359
|
+
* {@link sectionOf} over every note the format permits, plus whatever the
|
|
360
|
+
* repository names:
|
|
361
|
+
*
|
|
362
|
+
* 1. **Every declared content type.** `sectionOf` returns `fm.type` for a
|
|
363
|
+
* non-`doc` note, so `being`, `lore` and `weapongear` are sections *by
|
|
364
|
+
* construction*, configured or not.
|
|
365
|
+
* 2. **The type's own subtypes** — `rules`, `user-guide`, `reference` for a
|
|
366
|
+
* `doc`, since a `doc` routes by its subtype.
|
|
367
|
+
* 3. **The configured sections**, which may name one that is neither: `sohl`
|
|
368
|
+
* configures `credits` and `dev-docs`.
|
|
369
|
+
*
|
|
370
|
+
* Checking only (3) was #197's fix and keyed on configuration a consumer may
|
|
371
|
+
* legitimately not have: `sohl-thalorna` has no `site:` block at all, so five
|
|
372
|
+
* of its landings were refused for naming their own content type (#200).
|
|
373
|
+
*
|
|
374
|
+
* **The guard survives.** A misspelling is none of the three, so it still
|
|
375
|
+
* fails, with the near miss drawn from the whole union. What is deliberately no
|
|
376
|
+
* longer caught is a landing for a section that exists but is empty — which is
|
|
377
|
+
* legitimate, and is why the set is the types the format *declares* rather than
|
|
378
|
+
* the types *present in the tree*: `sohl` ships two such landings above tables
|
|
379
|
+
* that stay empty until the first note of each type does.
|
|
380
|
+
*
|
|
325
381
|
* @param {object} note - The note.
|
|
326
382
|
* @param {object} opts
|
|
327
383
|
* @param {string} opts.type - The note's type, for the message.
|
|
328
384
|
* @param {object} opts.entry - The type's vocabulary entry.
|
|
385
|
+
* @param {boolean} [opts.asSection] - Whether the value is a section address —
|
|
386
|
+
* see {@link subTypeIsSection}.
|
|
387
|
+
* @param {readonly string[]} [opts.types] - The content types the format
|
|
388
|
+
* declares. Empty leaves a landing checked against its genres and the
|
|
389
|
+
* configured sections alone.
|
|
390
|
+
* @param {readonly string[]} [opts.sections] - The sections the repository
|
|
391
|
+
* configures. Empty when it configures none, which is ordinary rather than a
|
|
392
|
+
* defect.
|
|
329
393
|
* @returns {object[]} Findings.
|
|
330
394
|
*/
|
|
331
|
-
function checkSubType(note, { type, entry }) {
|
|
395
|
+
function checkSubType(note, { type, entry, asSection = false, types = [], sections = [] }) {
|
|
332
396
|
const fm = note.fm ?? {};
|
|
333
397
|
if (!Object.hasOwn(fm, "subType") || fm.subType == null || fm.subType === "") return [];
|
|
334
398
|
|
|
@@ -353,6 +417,28 @@ function checkSubType(note, { type, entry }) {
|
|
|
353
417
|
// the value is nobody's to check yet.
|
|
354
418
|
if (values == null || values.includes(value)) return [];
|
|
355
419
|
|
|
420
|
+
if (asSection) {
|
|
421
|
+
if (types.includes(value) || sections.includes(value)) return [];
|
|
422
|
+
const guess = nearest(value, [...types, ...values, ...sections]);
|
|
423
|
+
return [
|
|
424
|
+
{
|
|
425
|
+
file: note.file,
|
|
426
|
+
...at,
|
|
427
|
+
severity: "error",
|
|
428
|
+
message:
|
|
429
|
+
`\`subType\` "${value}" is the section this README lands ` +
|
|
430
|
+
`at, and nothing declares it. A section is a content type ` +
|
|
431
|
+
`(${types.join(", ")}), a subtype ${type} declares ` +
|
|
432
|
+
`(${values.join(", ")})` +
|
|
433
|
+
(sections.length ?
|
|
434
|
+
`, or a section configured under \`site.sections\` / ` +
|
|
435
|
+
`\`site.readmeSections\` (${sections.join(", ")})`
|
|
436
|
+
: "") +
|
|
437
|
+
(guess ? `. Did you mean "${guess}"?` : ""),
|
|
438
|
+
},
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
|
|
356
442
|
const guess = nearest(value, values);
|
|
357
443
|
return [
|
|
358
444
|
{
|
|
@@ -435,9 +521,33 @@ function checkTags(note, { type }) {
|
|
|
435
521
|
* @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
|
|
436
522
|
* The system blocks to check, and what each accepts. See
|
|
437
523
|
* {@link DEFAULT_SYSTEM_BLOCKS}.
|
|
524
|
+
* @param {string} [opts.landing] - The repository's landing rule, from
|
|
525
|
+
* `publish.address.landing`. It decides which note addresses a whole section,
|
|
526
|
+
* and so whether a `subType` is a genre or an address (#197).
|
|
527
|
+
* @param {readonly string[]} [opts.types] - The content types the format
|
|
528
|
+
* declares — the sections that exist by construction (#200). Read from
|
|
529
|
+
* `docs/content-format.md` rather than from `schemas`, because the two answer
|
|
530
|
+
* different questions: whether an address is real, and whether this build can
|
|
531
|
+
* check a note's fields. A type the specification declares and no schema
|
|
532
|
+
* covers is a real section, and its notes are reported on their own account.
|
|
533
|
+
* @param {readonly string[]} [opts.sections] - The sections the repository
|
|
534
|
+
* configures, from `declaredSections`. Supplied by the caller for the same
|
|
535
|
+
* reason `vocabulary` is: this module checks a note against what it is
|
|
536
|
+
* handed.
|
|
438
537
|
* @returns {object[]} Findings, each with a locator where one is obtainable.
|
|
439
538
|
*/
|
|
440
|
-
export function lintNote(
|
|
539
|
+
export function lintNote(
|
|
540
|
+
note,
|
|
541
|
+
{
|
|
542
|
+
schemas,
|
|
543
|
+
index,
|
|
544
|
+
vocabulary,
|
|
545
|
+
systems = DEFAULT_SYSTEM_BLOCKS,
|
|
546
|
+
landing = DEFAULT_ADDRESS_SCHEME.landing,
|
|
547
|
+
types = [],
|
|
548
|
+
sections = [],
|
|
549
|
+
},
|
|
550
|
+
) {
|
|
441
551
|
const findings = [];
|
|
442
552
|
const fm = note.fm ?? {};
|
|
443
553
|
const type = String(fm.type ?? "");
|
|
@@ -534,7 +644,15 @@ export function lintNote(note, { schemas, index, vocabulary, systems = DEFAULT_S
|
|
|
534
644
|
const entry = vocabulary?.[type];
|
|
535
645
|
if (entry) {
|
|
536
646
|
findings.push(...checkDataContainer(note, { type, fields: entry.data ?? [] }));
|
|
537
|
-
findings.push(
|
|
647
|
+
findings.push(
|
|
648
|
+
...checkSubType(note, {
|
|
649
|
+
type,
|
|
650
|
+
entry,
|
|
651
|
+
asSection: subTypeIsSection(note, landing),
|
|
652
|
+
types,
|
|
653
|
+
sections,
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
538
656
|
}
|
|
539
657
|
|
|
540
658
|
const fields = authoredFields(schema);
|
|
@@ -691,10 +809,19 @@ export function lintNote(note, { schemas, index, vocabulary, systems = DEFAULT_S
|
|
|
691
809
|
* @param {boolean} [opts.references=true] - Whether to check references.
|
|
692
810
|
* @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
|
|
693
811
|
* The system blocks to check. See {@link DEFAULT_SYSTEM_BLOCKS}.
|
|
812
|
+
* @param {string} [opts.landing] - The repository's landing rule; see
|
|
813
|
+
* {@link lintNote}.
|
|
814
|
+
* @param {readonly string[]} [opts.types] - The content types the format
|
|
815
|
+
* declares; see {@link lintNote}.
|
|
816
|
+
* @param {readonly string[]} [opts.sections] - The sections the repository
|
|
817
|
+
* configures; see {@link lintNote}.
|
|
694
818
|
* @returns {{findings: object[], notes: number}} The findings, and how many
|
|
695
819
|
* notes were inspected.
|
|
696
820
|
*/
|
|
697
|
-
export function lintFrontmatter(
|
|
821
|
+
export function lintFrontmatter(
|
|
822
|
+
index,
|
|
823
|
+
{ schemas, vocabulary, references = true, systems, landing, types, sections },
|
|
824
|
+
) {
|
|
698
825
|
const findings = [];
|
|
699
826
|
const notes = [...index.notes].sort((a, b) =>
|
|
700
827
|
a.file < b.file ? -1
|
|
@@ -708,6 +835,9 @@ export function lintFrontmatter(index, { schemas, vocabulary, references = true,
|
|
|
708
835
|
vocabulary,
|
|
709
836
|
index: references ? index : undefined,
|
|
710
837
|
...(systems ? { systems } : {}),
|
|
838
|
+
...(landing ? { landing } : {}),
|
|
839
|
+
...(types ? { types } : {}),
|
|
840
|
+
...(sections ? { sections } : {}),
|
|
711
841
|
}),
|
|
712
842
|
);
|
|
713
843
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.1.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,30 @@ export function publishesContentPages(config: {
|
|
|
14
14
|
site: SiteMode;
|
|
15
15
|
};
|
|
16
16
|
}): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Every URL section this repository names, in declaration order (#197).
|
|
19
|
+
*
|
|
20
|
+
* A section is *declared* by describing it: `site.sections` for one whose
|
|
21
|
+
* landing this build generates, `site.readmeSections` for one whose landing is
|
|
22
|
+
* a `README`. Between them they are the open set of addresses a repository
|
|
23
|
+
* says it publishes under — which is what a `README` landing's `subType` names,
|
|
24
|
+
* since `sectionOf` reads that field as the section rather than as a genre.
|
|
25
|
+
*
|
|
26
|
+
* Read from the same two maps the site build renders each landing from, so the
|
|
27
|
+
* set a note is checked against and the set a landing is written from cannot
|
|
28
|
+
* come to disagree. A repository that describes no section declares none, and
|
|
29
|
+
* the answer is empty rather than a guess assembled from the tree.
|
|
30
|
+
*
|
|
31
|
+
* @param {{site: {sections: object, readmeSections: object}}} config - A
|
|
32
|
+
* resolved configuration.
|
|
33
|
+
* @returns {readonly string[]} The section names, deduplicated.
|
|
34
|
+
*/
|
|
35
|
+
export function declaredSections(config: {
|
|
36
|
+
site: {
|
|
37
|
+
sections: object;
|
|
38
|
+
readmeSections: object;
|
|
39
|
+
};
|
|
40
|
+
}): readonly string[];
|
|
17
41
|
/**
|
|
18
42
|
* Validate and normalize a content configuration.
|
|
19
43
|
*
|
|
@@ -29,9 +29,22 @@ export function matchesKind(value: unknown, kind: string): boolean;
|
|
|
29
29
|
* @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
|
|
30
30
|
* The system blocks to check, and what each accepts. See
|
|
31
31
|
* {@link DEFAULT_SYSTEM_BLOCKS}.
|
|
32
|
+
* @param {string} [opts.landing] - The repository's landing rule, from
|
|
33
|
+
* `publish.address.landing`. It decides which note addresses a whole section,
|
|
34
|
+
* and so whether a `subType` is a genre or an address (#197).
|
|
35
|
+
* @param {readonly string[]} [opts.types] - The content types the format
|
|
36
|
+
* declares — the sections that exist by construction (#200). Read from
|
|
37
|
+
* `docs/content-format.md` rather than from `schemas`, because the two answer
|
|
38
|
+
* different questions: whether an address is real, and whether this build can
|
|
39
|
+
* check a note's fields. A type the specification declares and no schema
|
|
40
|
+
* covers is a real section, and its notes are reported on their own account.
|
|
41
|
+
* @param {readonly string[]} [opts.sections] - The sections the repository
|
|
42
|
+
* configures, from `declaredSections`. Supplied by the caller for the same
|
|
43
|
+
* reason `vocabulary` is: this module checks a note against what it is
|
|
44
|
+
* handed.
|
|
32
45
|
* @returns {object[]} Findings, each with a locator where one is obtainable.
|
|
33
46
|
*/
|
|
34
|
-
export function lintNote(note: object, { schemas, index, vocabulary, systems }: {
|
|
47
|
+
export function lintNote(note: object, { schemas, index, vocabulary, systems, landing, types, sections, }: {
|
|
35
48
|
schemas: Record<string, readonly object[]>;
|
|
36
49
|
index?: object | undefined;
|
|
37
50
|
vocabulary?: Record<string, object> | undefined;
|
|
@@ -39,6 +52,9 @@ export function lintNote(note: object, { schemas, index, vocabulary, systems }:
|
|
|
39
52
|
known?: readonly string[];
|
|
40
53
|
fieldVocabulary?: boolean;
|
|
41
54
|
}>> | undefined;
|
|
55
|
+
landing?: string | undefined;
|
|
56
|
+
types?: readonly string[] | undefined;
|
|
57
|
+
sections?: readonly string[] | undefined;
|
|
42
58
|
}): object[];
|
|
43
59
|
/**
|
|
44
60
|
* Check every note in a built index against its type's schema.
|
|
@@ -51,10 +67,16 @@ export function lintNote(note: object, { schemas, index, vocabulary, systems }:
|
|
|
51
67
|
* @param {boolean} [opts.references=true] - Whether to check references.
|
|
52
68
|
* @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
|
|
53
69
|
* The system blocks to check. See {@link DEFAULT_SYSTEM_BLOCKS}.
|
|
70
|
+
* @param {string} [opts.landing] - The repository's landing rule; see
|
|
71
|
+
* {@link lintNote}.
|
|
72
|
+
* @param {readonly string[]} [opts.types] - The content types the format
|
|
73
|
+
* declares; see {@link lintNote}.
|
|
74
|
+
* @param {readonly string[]} [opts.sections] - The sections the repository
|
|
75
|
+
* configures; see {@link lintNote}.
|
|
54
76
|
* @returns {{findings: object[], notes: number}} The findings, and how many
|
|
55
77
|
* notes were inspected.
|
|
56
78
|
*/
|
|
57
|
-
export function lintFrontmatter(index: object, { schemas, vocabulary, references, systems }: {
|
|
79
|
+
export function lintFrontmatter(index: object, { schemas, vocabulary, references, systems, landing, types, sections }: {
|
|
58
80
|
schemas: Record<string, readonly object[]>;
|
|
59
81
|
vocabulary?: Record<string, object> | undefined;
|
|
60
82
|
references?: boolean | undefined;
|
|
@@ -62,6 +84,9 @@ export function lintFrontmatter(index: object, { schemas, vocabulary, references
|
|
|
62
84
|
known?: readonly string[];
|
|
63
85
|
fieldVocabulary?: boolean;
|
|
64
86
|
}>> | undefined;
|
|
87
|
+
landing?: string | undefined;
|
|
88
|
+
types?: readonly string[] | undefined;
|
|
89
|
+
sections?: readonly string[] | undefined;
|
|
65
90
|
}): {
|
|
66
91
|
findings: object[];
|
|
67
92
|
notes: number;
|