@heroiclands/package-build 6.0.0 → 7.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 +798 -0
- package/CONTENT.md +228 -4
- package/bin/content-build.mjs +196 -10
- package/bin/package-build.mjs +8 -1
- package/config.mjs +25 -3
- package/content-config.mjs +283 -29
- package/engine/address-diff.mjs +290 -0
- package/engine/base-compiler.mjs +25 -0
- package/engine/content-links.mjs +132 -27
- package/engine/content-lint.mjs +23 -2
- package/engine/diagnostics.mjs +61 -1
- package/engine/frontmatter-lint.mjs +22 -0
- package/engine/generate.mjs +10 -5
- package/engine/helpers.mjs +38 -0
- package/engine/homepage.mjs +206 -2
- package/engine/journals.mjs +8 -1
- package/engine/macros.mjs +2 -0
- package/engine/pack-config.mjs +143 -13
- package/engine/prose-lint.mjs +10 -2
- package/engine/scenes.mjs +2 -2
- package/engine/site-build.mjs +74 -19
- package/engine/web-wikilinks.mjs +13 -4
- package/engine/wikilink-syntax.mjs +25 -0
- package/engine/wikilinks.mjs +6 -3
- package/manifest.mjs +37 -2
- package/package.json +5 -3
- package/sohl/actors.mjs +23 -10
- package/sohl/items.mjs +1 -1
- package/types/content-config.d.mts +14 -0
- package/types/engine/address-diff.d.mts +108 -0
- package/types/engine/base-compiler.d.mts +18 -1
- package/types/engine/content-links.d.mts +11 -3
- package/types/engine/content-lint.d.mts +4 -1
- package/types/engine/diagnostics.d.mts +33 -1
- package/types/engine/generate.d.mts +3 -2
- package/types/engine/helpers.d.mts +29 -3
- package/types/engine/homepage.d.mts +117 -0
- package/types/engine/journals.d.mts +7 -1
- package/types/engine/pack-config.d.mts +22 -0
- package/types/engine/prose-lint.d.mts +10 -2
- package/types/engine/site-build.d.mts +28 -3
- package/types/engine/wikilink-syntax.d.mts +24 -0
- package/types/sohl/actors.d.mts +3 -3
|
@@ -131,6 +131,20 @@ export const DEFAULT_ADDRESS_SCHEME: Readonly<{
|
|
|
131
131
|
* @satisfies {readonly SiteMode[]}
|
|
132
132
|
*/
|
|
133
133
|
export const SITE_MODES: readonly ["homepage", "content"];
|
|
134
|
+
/**
|
|
135
|
+
* How the loader hands {@link defineConfig} the system version it resolved.
|
|
136
|
+
*
|
|
137
|
+
* A **Symbol**, deliberately. `stats.systemVersion` is refused from an authored
|
|
138
|
+
* configuration (#48), but the value still has to reach here from the loader —
|
|
139
|
+
* which is the half that may do I/O, and which reads a system package's version
|
|
140
|
+
* out of the adjacent `package.json`. A string key would be a second spelling of
|
|
141
|
+
* the refused one, forgeable from YAML and reachable by `rejectUnknownKeys`; a
|
|
142
|
+
* symbol key cannot be written in YAML at all and does not appear in
|
|
143
|
+
* `Object.keys`, so the refusal has no back door.
|
|
144
|
+
*
|
|
145
|
+
* @type {symbol}
|
|
146
|
+
*/
|
|
147
|
+
export const DERIVED_SYSTEM_VERSION: symbol;
|
|
134
148
|
/**
|
|
135
149
|
* How much of a package reaches the web.
|
|
136
150
|
*
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The address space a set of compiled Item pack directories publishes.
|
|
3
|
+
*
|
|
4
|
+
* The directories are read as one space for the same reason the actors pass
|
|
5
|
+
* reads them as one: a being names an item by `(type, shortcode)` and never by
|
|
6
|
+
* the pack it happens to ship in. Both sides of a diff are built by this one
|
|
7
|
+
* function, so a released catalogue extracted by `deps fetch` and a freshly
|
|
8
|
+
* compiled pack are indexed identically and a difference between them is a real
|
|
9
|
+
* one rather than an artefact of two readers.
|
|
10
|
+
*
|
|
11
|
+
* A missing directory throws rather than reading as an empty space: an empty
|
|
12
|
+
* baseline would report every address in the package as withdrawn, and an empty
|
|
13
|
+
* current side would report every address as gone — the loudest possible
|
|
14
|
+
* output from the quietest possible mistake.
|
|
15
|
+
*
|
|
16
|
+
* @param {readonly string[]} dirs - Directories of item JSON.
|
|
17
|
+
* @returns {Map<string, {id: string, name: string, type: string, shortcode: string, file: string}>}
|
|
18
|
+
* Every item, keyed `type:shortcode`.
|
|
19
|
+
*/
|
|
20
|
+
export function readItemAddresses(dirs: readonly string[]): Map<string, {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
shortcode: string;
|
|
25
|
+
file: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Every address the baseline published that this build does not.
|
|
29
|
+
*
|
|
30
|
+
* An address that merely *arrived* is not a finding: adding one breaks nobody.
|
|
31
|
+
* The arrivals are read only to answer the one question that matters about a
|
|
32
|
+
* departure — is the document still here under another name?
|
|
33
|
+
*
|
|
34
|
+
* @param {Map<string, object>} baseline - The released address space.
|
|
35
|
+
* @param {Map<string, object>} current - This build's address space.
|
|
36
|
+
* @param {object} opts
|
|
37
|
+
* @param {string} opts.baseline - What the baseline is, for the message —
|
|
38
|
+
* conventionally `<package>@<version>`.
|
|
39
|
+
* @returns {Array<object>} One finding per departed address, in address order
|
|
40
|
+
* so two runs read the same. `kind` is `"renamed"` (with `to`) or
|
|
41
|
+
* `"withdrawn"`.
|
|
42
|
+
*/
|
|
43
|
+
export function diffItemAddresses(baseline: Map<string, object>, current: Map<string, object>, { baseline: label }: {
|
|
44
|
+
baseline: string;
|
|
45
|
+
}): Array<object>;
|
|
46
|
+
/**
|
|
47
|
+
* Every content note in a tree, indexed by the document id it authors.
|
|
48
|
+
*
|
|
49
|
+
* The address space is read from compiled output because that is what actually
|
|
50
|
+
* ships; the tree is read only to place a finding somewhere a reader can open
|
|
51
|
+
* and fix it. Each source answers the question it is good at, and the id is the
|
|
52
|
+
* exact key that joins them.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} contentBase - Root of the content tree.
|
|
55
|
+
* @param {object} [opts]
|
|
56
|
+
* @param {readonly string[]} [opts.skipDirectories] - Passed to the walk.
|
|
57
|
+
* @returns {Map<string, string>} Document id → the note's absolute path.
|
|
58
|
+
*/
|
|
59
|
+
export function noteFilesById(contentBase: string, { skipDirectories }?: {
|
|
60
|
+
skipDirectories?: readonly string[] | undefined;
|
|
61
|
+
}): Map<string, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Where to send the reader for one finding.
|
|
64
|
+
*
|
|
65
|
+
* A rename is fixed in the note that made it, so a finding whose id is still in
|
|
66
|
+
* this tree is reported at that note's `shortcode:` line — the line the author
|
|
67
|
+
* just edited. A withdrawal has no such note by definition, so it degrades to
|
|
68
|
+
* the baseline document, which is the only artefact left that records the
|
|
69
|
+
* address existing. When neither is readable the position is **dropped**, never
|
|
70
|
+
* defaulted to `1:1`.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} finding - One finding from {@link diffItemAddresses}.
|
|
73
|
+
* @param {Map<string, string>} noteFiles - From {@link noteFilesById}.
|
|
74
|
+
* @returns {{file?: string, line?: number, column?: number}} Spreadable
|
|
75
|
+
* position fields for {@link formatDiagnostic}.
|
|
76
|
+
*/
|
|
77
|
+
export function locateAddressFinding(finding: object, noteFiles: Map<string, string>): {
|
|
78
|
+
file?: string;
|
|
79
|
+
line?: number;
|
|
80
|
+
column?: number;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* What one finding says, without a locator or a severity.
|
|
84
|
+
*
|
|
85
|
+
* The rename message names the identity it matched on, because that is what
|
|
86
|
+
* separates this from a spelling suggestion: the reader can check the id in
|
|
87
|
+
* both artefacts. The withdrawal message names no successor, because none is
|
|
88
|
+
* known — and says so, rather than leaving the reader to wonder whether one was
|
|
89
|
+
* looked for.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} finding - One finding from {@link diffItemAddresses}.
|
|
92
|
+
* @returns {string} The message.
|
|
93
|
+
*/
|
|
94
|
+
export function addressFindingMessage(finding: object): string;
|
|
95
|
+
/**
|
|
96
|
+
* One finding, in the standard `file:line:column: severity: message` form.
|
|
97
|
+
*
|
|
98
|
+
* @param {object} finding - One finding from {@link diffItemAddresses}.
|
|
99
|
+
* @param {{file?: string, line?: number, column?: number}} at - From
|
|
100
|
+
* {@link locateAddressFinding}.
|
|
101
|
+
* @param {"warning"|"error"} [severity] - `error` when the caller is gating.
|
|
102
|
+
* @returns {string} The formatted diagnostic, path first on the line.
|
|
103
|
+
*/
|
|
104
|
+
export function formatAddressFinding(finding: object, at: {
|
|
105
|
+
file?: string;
|
|
106
|
+
line?: number;
|
|
107
|
+
column?: number;
|
|
108
|
+
}, severity?: "warning" | "error"): string;
|
|
@@ -92,7 +92,7 @@ export class BasePackCompiler {
|
|
|
92
92
|
* @param {boolean} [options.routingReporter] - Whether this pass reports a
|
|
93
93
|
* note of its type that routes nowhere.
|
|
94
94
|
*/
|
|
95
|
-
constructor({ contentBase, dest, folderResolver, packName, docType, router, routingReporter, }?: {
|
|
95
|
+
constructor({ contentBase, dest, folderResolver, packName, packSystem, docType, router, routingReporter, }?: {
|
|
96
96
|
contentBase: string;
|
|
97
97
|
dest: string;
|
|
98
98
|
folderResolver?: ((path: string | null) => string | null) | undefined;
|
|
@@ -153,6 +153,22 @@ export class BasePackCompiler {
|
|
|
153
153
|
* @type {number}
|
|
154
154
|
*/
|
|
155
155
|
unresolvedLinks: number;
|
|
156
|
+
packSystem: any;
|
|
157
|
+
/**
|
|
158
|
+
* The `_stats` block every entry this pass emits is stamped with (#48).
|
|
159
|
+
*
|
|
160
|
+
* Per pack rather than per package, because a module may ship the same
|
|
161
|
+
* content for two systems — `harn-ensemble` has an `actors-hm3` pack and an
|
|
162
|
+
* `actors-sohl` pack — and those documents were built against different
|
|
163
|
+
* system versions. A single global block stamped both identically.
|
|
164
|
+
*
|
|
165
|
+
* Memoised on the instance: one pass, one pack, one system, so the block is
|
|
166
|
+
* constant for the life of the compiler. The previous module-level memo
|
|
167
|
+
* could not be, because it was shared across passes for different packs.
|
|
168
|
+
*
|
|
169
|
+
* @returns {object} The block, built once per compiler.
|
|
170
|
+
*/
|
|
171
|
+
get stats(): object;
|
|
156
172
|
/**
|
|
157
173
|
* Whether this pass's pack is the one a claimed note belongs in.
|
|
158
174
|
*
|
|
@@ -326,6 +342,7 @@ export class BasePackCompiler {
|
|
|
326
342
|
bodyLine: number | undefined;
|
|
327
343
|
bodyColumn: number | undefined;
|
|
328
344
|
} | undefined;
|
|
345
|
+
#private;
|
|
329
346
|
}
|
|
330
347
|
/**
|
|
331
348
|
* The tallies one pass accumulates while walking the tree.
|
|
@@ -46,9 +46,17 @@ export function buildLinkIndex(contentBase: string, { manifestDir, skipDirectori
|
|
|
46
46
|
* and what replaced it, so this is a fact rather than a guess — and it is
|
|
47
47
|
* exactly the SoHL defect.
|
|
48
48
|
* - A **hardcoded absolute URL** into this package's own prefix, or into one a
|
|
49
|
-
* vendored manifest names.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* vendored manifest names. Every one of them has a better form to write, which
|
|
50
|
+
* is why every one is reported — including a bare `/<package>/`, which names
|
|
51
|
+
* another package's landing (#87).
|
|
52
|
+
*
|
|
53
|
+
* That last case was exempt until the better form was identified, on the
|
|
54
|
+
* reasoning that a landing is in no link manifest so nothing could resolve it.
|
|
55
|
+
* True, and beside the point: it does not need resolving. A landing's address
|
|
56
|
+
* *is* its package prefix, so `/<package>/` is the absolute URL with the host
|
|
57
|
+
* struck off — host-free, emitted verbatim, and needing no index, which is
|
|
58
|
+
* what lets it hold in homepage-only mode where the tree is never walked. The
|
|
59
|
+
* form was already accepted here; nothing had ever named it as the one to use.
|
|
52
60
|
* - A **root-relative `url:`**, which the theme's `relURL` prefixes a second
|
|
53
61
|
* time. `href:` means "already resolved, use verbatim", so the same leading
|
|
54
62
|
* slash is correct there and is not reported.
|
|
@@ -16,12 +16,15 @@ export function isValidShortcode(value: unknown): boolean;
|
|
|
16
16
|
* @param {object} [opts]
|
|
17
17
|
* @param {readonly string[]} [opts.skipDirectories] - Directory names the walk
|
|
18
18
|
* ignores. Defaults to the configured list.
|
|
19
|
+
* @param {string} [opts.contentPackage] - The package this tree builds, for the
|
|
20
|
+
* homepage rule. Dropped from that finding when unknown rather than guessed.
|
|
19
21
|
* @returns {{findings: Array<{file: string, line?: number, column?: number,
|
|
20
22
|
* severity: "error"|"warning", message: string}>, notes: number,
|
|
21
23
|
* keys: number}} The findings, and what was inspected to produce them.
|
|
22
24
|
*/
|
|
23
|
-
export function lintContentTree(contentBase: string, { skipDirectories }?: {
|
|
25
|
+
export function lintContentTree(contentBase: string, { skipDirectories, contentPackage }?: {
|
|
24
26
|
skipDirectories?: readonly string[] | undefined;
|
|
27
|
+
contentPackage?: string | undefined;
|
|
25
28
|
}): {
|
|
26
29
|
findings: Array<{
|
|
27
30
|
file: string;
|
|
@@ -169,12 +169,44 @@ export function positionOfLiteral(text: string, needle: string, occurrence?: num
|
|
|
169
169
|
* resolves to nothing — yields `{}`, so a caller spreads the result and the
|
|
170
170
|
* position is dropped rather than guessed.
|
|
171
171
|
*
|
|
172
|
+
* **`key: true` addresses the declaration rather than the value.** A finding
|
|
173
|
+
* about a *value* — this pack name is not in `packs[]` — belongs on the value,
|
|
174
|
+
* which is the default. A finding that names a **field** — `\`site.sections.x\`
|
|
175
|
+
* is not a recognized option` — sends the reader to look for that field, so the
|
|
176
|
+
* position should be the field's own, and in a flow mapping
|
|
177
|
+
* (`{ title: X, banner: Y }`) the two are different columns on one line. The
|
|
178
|
+
* key's node is found through the same parse, so this stays one locator rather
|
|
179
|
+
* than a second one free to disagree with it.
|
|
180
|
+
*
|
|
172
181
|
* @param {string} text - The document's contents.
|
|
173
182
|
* @param {ReadonlyArray<string|number>} keyPath - Path to the node: map keys as
|
|
174
183
|
* strings, sequence entries as numbers.
|
|
184
|
+
* @param {object} [opts]
|
|
185
|
+
* @param {boolean} [opts.key=false] - Report where the last segment is
|
|
186
|
+
* *declared* rather than where its value sits. Ignored for a sequence entry,
|
|
187
|
+
* which has no key.
|
|
175
188
|
* @returns {{line?: number, column?: number}} Spreadable position fields.
|
|
176
189
|
*/
|
|
177
|
-
export function positionOfYamlPath(text: string, keyPath: ReadonlyArray<string | number
|
|
190
|
+
export function positionOfYamlPath(text: string, keyPath: ReadonlyArray<string | number>, { key }?: {
|
|
191
|
+
key?: boolean | undefined;
|
|
192
|
+
}): {
|
|
178
193
|
line?: number;
|
|
179
194
|
column?: number;
|
|
180
195
|
};
|
|
196
|
+
/**
|
|
197
|
+
* The YAML key path a **dotted field path** addresses.
|
|
198
|
+
*
|
|
199
|
+
* Configuration checks report the offending key as the path a reader would
|
|
200
|
+
* write it — `packs[1].name`, `site.sections.affliction.title` — because that
|
|
201
|
+
* is what the message has to say. {@link positionOfYamlPath} addresses a node
|
|
202
|
+
* by segments instead, so this is the one translation between them: `.`
|
|
203
|
+
* separates map keys, and a bracketed suffix is a sequence index.
|
|
204
|
+
*
|
|
205
|
+
* A path this cannot parse yields `[]`, which {@link positionOfYamlPath} in
|
|
206
|
+
* turn resolves to no position — dropped rather than guessed, as everything
|
|
207
|
+
* else here is.
|
|
208
|
+
*
|
|
209
|
+
* @param {string} field - The dotted path, as a diagnostic spells it.
|
|
210
|
+
* @returns {Array<string|number>} Its segments, sequence indices as numbers.
|
|
211
|
+
*/
|
|
212
|
+
export function yamlKeyPath(field: string): Array<string | number>;
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* @param {object} [config] - The resolved build configuration. Defaults to this
|
|
14
14
|
* repository's.
|
|
15
15
|
* @returns {string[]} Each Item pack's JSON directory. Empty when the
|
|
16
|
-
* repository ships no items at all
|
|
17
|
-
*
|
|
16
|
+
* repository ships no items at all, which is a legitimate package: the actors
|
|
17
|
+
* pass accepts an empty list and reports an item it cannot resolve per
|
|
18
|
+
* `(type, shortcode)` instead, naming the being (#49).
|
|
18
19
|
*/
|
|
19
20
|
export function itemPackJsonDirs(config?: object): string[];
|
|
20
21
|
/**
|
|
@@ -19,7 +19,7 @@ export function parseMarkdownFile(filePath: any): {
|
|
|
19
19
|
} | {
|
|
20
20
|
frontmatter: any;
|
|
21
21
|
body: string;
|
|
22
|
-
description:
|
|
22
|
+
description: string;
|
|
23
23
|
bodyLine: number;
|
|
24
24
|
bodyColumn: number;
|
|
25
25
|
};
|
|
@@ -56,7 +56,7 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories }?: {
|
|
|
56
56
|
absPath: string;
|
|
57
57
|
frontmatter: any;
|
|
58
58
|
body: string;
|
|
59
|
-
description:
|
|
59
|
+
description: string;
|
|
60
60
|
bodyLine: number;
|
|
61
61
|
bodyColumn: number;
|
|
62
62
|
}, void, unknown>;
|
|
@@ -190,6 +190,32 @@ export function buildStats(systemVersion?: string, config?: {
|
|
|
190
190
|
packageManifest: string;
|
|
191
191
|
};
|
|
192
192
|
}): object;
|
|
193
|
+
/**
|
|
194
|
+
* The `_stats` block for one pack, stamped with the system that pack is for
|
|
195
|
+
* (#48).
|
|
196
|
+
*
|
|
197
|
+
* **`systemId` travels with `systemVersion`.** They are one decision, so where
|
|
198
|
+
* one is omitted both are. Stamping a per-pack version against a package-wide
|
|
199
|
+
* id would emit `systemId: sohl, systemVersion: 1.6.3` on HM3 documents — a
|
|
200
|
+
* *plausible lie*, which is worse than the missing value #43 fixed, because
|
|
201
|
+
* nothing about it looks wrong.
|
|
202
|
+
*
|
|
203
|
+
* Resolution, in order:
|
|
204
|
+
*
|
|
205
|
+
* 1. The pack's own `system:`, looked up in the `systems:` block. That is the
|
|
206
|
+
* case a module shipping for two systems needs, and the one no
|
|
207
|
+
* package-wide value could express.
|
|
208
|
+
* 2. Failing that, the package-wide `stats` — a package whose packs are all for
|
|
209
|
+
* one system, which is every package that worked before this existed.
|
|
210
|
+
*
|
|
211
|
+
* A pack naming a system is validated against `systems:` at configuration time,
|
|
212
|
+
* so an unresolvable name never reaches here.
|
|
213
|
+
*
|
|
214
|
+
* @param {string|null|undefined} packSystem - The pack's declared `system:`.
|
|
215
|
+
* @param {object} [config] - The resolved configuration.
|
|
216
|
+
* @returns {object} The `_stats` block for that pack.
|
|
217
|
+
*/
|
|
218
|
+
export function statsForPack(packSystem: string | null | undefined, config?: object): object;
|
|
193
219
|
/**
|
|
194
220
|
* The `_stats` block every compiler stamps on an entry it emits, built once.
|
|
195
221
|
*
|
|
@@ -342,7 +368,7 @@ export function folderFilename(name: any, id: any): string;
|
|
|
342
368
|
* pack, `"JournalEntry"` for the journals pack.
|
|
343
369
|
*/
|
|
344
370
|
export function writeFolderDocs(folders: any, stats: any, destDir: any, documentType: any): void;
|
|
345
|
-
export const md:
|
|
371
|
+
export const md: import("markdown-it").MarkdownIt;
|
|
346
372
|
export { slugify } from "./content-slug.mjs";
|
|
347
373
|
export { makeId } from "./ids.mjs";
|
|
348
374
|
export { getFrontmatter, sohlField, resolveCharges, resolveSkillAptitudes, resolveRelation, requireSubType, parseValueDesc } from "./frontmatter.mjs";
|
|
@@ -5,6 +5,87 @@
|
|
|
5
5
|
* @returns {boolean} Whether it is a homepage note.
|
|
6
6
|
*/
|
|
7
7
|
export function isHomepage(fm: object | null | undefined): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The address-bearing fields one note authors, in the order it authored them.
|
|
10
|
+
*
|
|
11
|
+
* Authoring order rather than declaration order, so a caller emitting one
|
|
12
|
+
* diagnostic per finding emits them top to bottom down the file — the order a
|
|
13
|
+
* reader and a compiler-output parser both expect.
|
|
14
|
+
*
|
|
15
|
+
* Presence is the whole test: `shortcode:` authored empty still says "this page
|
|
16
|
+
* has an address of its own", and a value cannot make the claim true.
|
|
17
|
+
*
|
|
18
|
+
* Returned without a locator, because the two things that would supply one —
|
|
19
|
+
* the raw note text and the position helper — belong to the caller. This
|
|
20
|
+
* mirrors {@link module:engine/retired-fields}, whose retired-field messages
|
|
21
|
+
* are likewise positioned by whoever reports them.
|
|
22
|
+
*
|
|
23
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
24
|
+
* @returns {Array<{key: string, message: string}>} One entry per field the note
|
|
25
|
+
* authored, empty for any note that is not a homepage.
|
|
26
|
+
*/
|
|
27
|
+
export function checkHomepageAddressFields(fm: object | null | undefined): Array<{
|
|
28
|
+
key: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Require exactly one homepage note in a content tree (#52).
|
|
33
|
+
*
|
|
34
|
+
* "Exactly one" is two rules, and they are **one severity** because they are
|
|
35
|
+
* one defect: a package whose front page is not the page a person chose.
|
|
36
|
+
*
|
|
37
|
+
* - _None_ and the package serves nothing at `/<package>/`. That is the failure
|
|
38
|
+
* #50 exists to prevent, and it is silent — the site build reports `wrote 0
|
|
39
|
+
* homepage(s)` and exits 0.
|
|
40
|
+
* - _Two_ and it serves a page nobody chose. Every homepage is written to the
|
|
41
|
+
* same {@link HOMEPAGE_DESTINATION}, so the second overwrites the first and
|
|
42
|
+
* the package's front page is decided by the order the walk happened to reach
|
|
43
|
+
* the files in — by *filename*, on a type whose whole point is that it is
|
|
44
|
+
* routed by frontmatter. There is no "first wins" convention to fall back on,
|
|
45
|
+
* so nothing here can pick the right one.
|
|
46
|
+
*
|
|
47
|
+
* Neither has a safe default, so neither is a warning. A warning is the right
|
|
48
|
+
* severity for something a build can proceed past correctly, and a build that
|
|
49
|
+
* proceeds past either of these publishes the wrong front page while reporting
|
|
50
|
+
* success — which is the exact outcome a warning would be tolerating.
|
|
51
|
+
*
|
|
52
|
+
* **Two is reported once per note, not once for the tree.** Each note is a
|
|
53
|
+
* place an author has to open and edit, and a single finding saying "there are
|
|
54
|
+
* two" sends them hunting for the second.
|
|
55
|
+
*
|
|
56
|
+
* **None is located at the tree, honestly.** There is no file to name, so the
|
|
57
|
+
* locator is the content root — the directory the note is missing from, which
|
|
58
|
+
* is a real path and the one the author adds it to. No line and no column are
|
|
59
|
+
* invented for it, per the diagnostic rules in
|
|
60
|
+
* {@link module:engine/diagnostics}. {@link lintContentTree} already reports an
|
|
61
|
+
* empty walk against the same locator.
|
|
62
|
+
*
|
|
63
|
+
* The rule reads no `site:` configuration and does not vary by
|
|
64
|
+
* `publish.site`: that setting chooses whether the *content* surfaces are
|
|
65
|
+
* published, and the homepage is the floor underneath both modes.
|
|
66
|
+
*
|
|
67
|
+
* @param {ReadonlyArray<{file: string}>} found - The homepage notes, in walk
|
|
68
|
+
* order. Paths may be absolute or relative to the working directory.
|
|
69
|
+
* @param {object} options - Options.
|
|
70
|
+
* @param {string} options.contentBase - Root of the content tree, for the
|
|
71
|
+
* locator when there is no file to name.
|
|
72
|
+
* @param {string} [options.contentPackage] - The package this tree builds.
|
|
73
|
+
* Dropped from the message when unknown rather than guessed.
|
|
74
|
+
* @returns {Array<{file: string, line?: number, column?: number,
|
|
75
|
+
* severity: "error", message: string}>} The findings, one per offending note.
|
|
76
|
+
*/
|
|
77
|
+
export function checkHomepageCount(found: ReadonlyArray<{
|
|
78
|
+
file: string;
|
|
79
|
+
}>, { contentBase, contentPackage }: {
|
|
80
|
+
contentBase: string;
|
|
81
|
+
contentPackage?: string | undefined;
|
|
82
|
+
}): Array<{
|
|
83
|
+
file: string;
|
|
84
|
+
line?: number;
|
|
85
|
+
column?: number;
|
|
86
|
+
severity: "error";
|
|
87
|
+
message: string;
|
|
88
|
+
}>;
|
|
8
89
|
/**
|
|
9
90
|
* The title a homepage publishes under.
|
|
10
91
|
*
|
|
@@ -107,6 +188,42 @@ export const HOMEPAGE_FIELDS: readonly import("./field-spec.mjs").FieldSpec[];
|
|
|
107
188
|
* @type {string}
|
|
108
189
|
*/
|
|
109
190
|
export const HOMEPAGE_DESTINATION: string;
|
|
191
|
+
/**
|
|
192
|
+
* The top-level fields a homepage refuses, and what each one would decide (#53).
|
|
193
|
+
*
|
|
194
|
+
* A note's URL derives from `name.full` and its identity from
|
|
195
|
+
* `(type, shortcode)`. The homepage is the one page for which neither holds: it
|
|
196
|
+
* publishes at `/<package>/`, fixed by the package id. An author fluent in the
|
|
197
|
+
* conventions writes them here expecting exactly what they do everywhere else,
|
|
198
|
+
* and gets none of it.
|
|
199
|
+
*
|
|
200
|
+
* **They were never inert, which is why ignoring them was the wrong answer.** A
|
|
201
|
+
* `shortcode` puts the note in the address index and in the `dataview` link
|
|
202
|
+
* universe, so `[[homepage-<shortcode>]]` resolves *green* — to
|
|
203
|
+
* `homepage/<slug>/`, an address derived from `name.full` and published by
|
|
204
|
+
* nothing, because a homepage is written to {@link HOMEPAGE_DESTINATION} at the
|
|
205
|
+
* package root. A build that reports a live link to a 404 is worse than one
|
|
206
|
+
* that says nothing. It also inflates `content-build lint`'s address tally, so
|
|
207
|
+
* the lint and the link manifest disagree about what the package publishes.
|
|
208
|
+
*
|
|
209
|
+
* **A named class, not an allow-list, and that boundary is the decision.** The
|
|
210
|
+
* documented envelope is `type` plus an optional `title`, and `landing`,
|
|
211
|
+
* `description` and `banner` are legitimate beside them — but a homepage's
|
|
212
|
+
* frontmatter is *emitted into the published page*
|
|
213
|
+
* ({@link homepageFrontmatter}), so an unrecognised key is a Hugo or theme
|
|
214
|
+
* parameter this build has never heard of and has no standing to refuse.
|
|
215
|
+
* Rejecting unknown keys would make every new theme parameter wait on a
|
|
216
|
+
* package-build release. What is refused is the specific class that makes a
|
|
217
|
+
* false claim about *where this page is*.
|
|
218
|
+
*
|
|
219
|
+
* `aliases` is deliberately not in the class: {@link homepageFrontmatter}
|
|
220
|
+
* already drops it from every emitted page, with a reason of its own, so
|
|
221
|
+
* authoring one is the same no-op it is on any other page rather than a wrong
|
|
222
|
+
* belief about this one's address.
|
|
223
|
+
*
|
|
224
|
+
* @type {ReadonlyMap<string, string>}
|
|
225
|
+
*/
|
|
226
|
+
export const HOMEPAGE_REFUSED_FIELDS: ReadonlyMap<string, string>;
|
|
110
227
|
/**
|
|
111
228
|
* The two frontmatter keys that hold an address, and what each one means.
|
|
112
229
|
*
|
|
@@ -93,15 +93,21 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
|
|
|
93
93
|
* heading; see {@link splitPages}.
|
|
94
94
|
* @param {string|null} [params.folder] - The folder id, or `null`.
|
|
95
95
|
* @param {object} [params.flags] - Document flags.
|
|
96
|
+
* @param {object} [params.stats] - The `_stats` block to stamp. Passed by the
|
|
97
|
+
* caller because it is a property of the *pack* being written, not of the
|
|
98
|
+
* entry: a module may ship the same content for two systems, and each pack's
|
|
99
|
+
* documents record the system version they were built against (#48). A
|
|
100
|
+
* caller with no pack in hand gets the package-wide block.
|
|
96
101
|
* @returns {object} The JournalEntry document, keyed for the pack.
|
|
97
102
|
*/
|
|
98
|
-
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, }: {
|
|
103
|
+
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats, }: {
|
|
99
104
|
id: string;
|
|
100
105
|
name: string;
|
|
101
106
|
markdown: string;
|
|
102
107
|
leadName?: string | undefined;
|
|
103
108
|
folder?: string | null | undefined;
|
|
104
109
|
flags?: object | undefined;
|
|
110
|
+
stats?: object | undefined;
|
|
105
111
|
}): object;
|
|
106
112
|
export class Journals extends BasePackCompiler {
|
|
107
113
|
/**
|
|
@@ -14,6 +14,28 @@
|
|
|
14
14
|
* {@link CONFIG_FILENAMES}.
|
|
15
15
|
*/
|
|
16
16
|
export function findConfigFile(from: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Attach the position of the key a configuration error names.
|
|
19
|
+
*
|
|
20
|
+
* Eighty-one checks across `content-config.mjs` and `config.mjs` report through
|
|
21
|
+
* one `fail()`, which knows the offending key's dotted path and nothing
|
|
22
|
+
* about where it was written. Locating one of them and not the rest would be
|
|
23
|
+
* worse than locating none — a reader would learn that some configuration
|
|
24
|
+
* errors carry a position and could not predict which — so the path rides on
|
|
25
|
+
* the error and every one of them is located here, at the boundary that knows
|
|
26
|
+
* which file was read (#95).
|
|
27
|
+
*
|
|
28
|
+
* The message keeps its body and gains the `file:line:column: error: ` prefix
|
|
29
|
+
* every other finding in this build already uses, so nothing a reader has today
|
|
30
|
+
* is lost. `located` marks it done, so an error crossing two boundaries is
|
|
31
|
+
* decorated once; the fields are also left on the error, for a caller that
|
|
32
|
+
* wants to re-render it.
|
|
33
|
+
*
|
|
34
|
+
* @param {unknown} err - What was thrown.
|
|
35
|
+
* @param {string} [configPath] - The configuration file that was read.
|
|
36
|
+
* @returns {unknown} The same error, decorated when it named a field.
|
|
37
|
+
*/
|
|
38
|
+
export function locateConfigError(err: unknown, configPath?: string): unknown;
|
|
17
39
|
/**
|
|
18
40
|
* Turn a parsed YAML configuration into the frozen one the engine reads.
|
|
19
41
|
*
|
|
@@ -36,8 +36,16 @@ export function checkFormatting(root: string, opts?: {
|
|
|
36
36
|
*
|
|
37
37
|
* {@link MARKDOWNLINT_CONFIG} is passed as markdownlint's `optionsDefault`,
|
|
38
38
|
* which is precisely the "shipped default, consumer overrides" behaviour the
|
|
39
|
-
* command promises: a
|
|
40
|
-
* and a
|
|
39
|
+
* command promises: a repository with no configuration of its own gets these
|
|
40
|
+
* rules, and a `.markdownlint-cli2.jsonc` found in the tree overrides them.
|
|
41
|
+
*
|
|
42
|
+
* The override is **key by key, and each key wholesale** — which is not the same
|
|
43
|
+
* as "replaces it", and the difference is the one worth stating. A consumer file
|
|
44
|
+
* declaring only `ignores` keeps this rule set intact, including `default: false`
|
|
45
|
+
* and every per-rule option; but its `ignores` *replaces*
|
|
46
|
+
* {@link MARKDOWN_IGNORES} rather than extending it, so such a file must restate
|
|
47
|
+
* every shared entry it still wants. Omitting `CHANGELOG.md` there silently
|
|
48
|
+
* starts linting a generated file.
|
|
41
49
|
*
|
|
42
50
|
* @param {string} root - Repository to lint.
|
|
43
51
|
* @param {object} [opts]
|
|
@@ -50,9 +50,9 @@ export function collectTreePages(tree: object, ctx: object): {
|
|
|
50
50
|
* packages ship under is a property of the code path rather than of a
|
|
51
51
|
* configuration that happens to be empty (#55).
|
|
52
52
|
*
|
|
53
|
-
* Returned as a list rather than as the one note there should be
|
|
54
|
-
*
|
|
55
|
-
* found
|
|
53
|
+
* Returned as a list rather than as the one note there should be, because the
|
|
54
|
+
* count is what {@link checkHomepageCount} judges (#52) — this walk reports
|
|
55
|
+
* what it found, and {@link buildSite} decides whether that is one.
|
|
56
56
|
*
|
|
57
57
|
* @param {string} contentBase - Absolute path to the content tree.
|
|
58
58
|
* @param {object} ctx - `{ skipDirectories }`.
|
|
@@ -129,6 +129,31 @@ export function gatesFailed(gates: any): boolean;
|
|
|
129
129
|
* @returns {Map<string, object[]>} Package → the notes it may tabulate.
|
|
130
130
|
*/
|
|
131
131
|
export function tableUniverse(pages: object[]): Map<string, object[]>;
|
|
132
|
+
/**
|
|
133
|
+
* The front matter a section's landing states about itself.
|
|
134
|
+
*
|
|
135
|
+
* The section metadata a configuration resolved, ready to be written or merged
|
|
136
|
+
* onto a page. Two things happen here and nothing else does:
|
|
137
|
+
*
|
|
138
|
+
* - **`title` leads.** It is the one key every landing has carried since the
|
|
139
|
+
* first one, and a landing whose block opened with `banner:` would be a
|
|
140
|
+
* gratuitous diff on every consumer's tree.
|
|
141
|
+
* - **An absent value is left off**, not written as `undefined` — which is not
|
|
142
|
+
* a value YAML can carry, and would abort the serializer.
|
|
143
|
+
*
|
|
144
|
+
* Everything else the section declared is passed through. That is the point of
|
|
145
|
+
* the function: before #91 both writers transcribed `title` and `banner` by
|
|
146
|
+
* name, so the vocabulary lived in three places — the schema that admits a key
|
|
147
|
+
* and the two writers that copy it — and a key added to the schema alone
|
|
148
|
+
* validated cleanly and then reached no page. The *schema* is the bound worth
|
|
149
|
+
* keeping (see `normalizeSectionMeta`, which refuses a key it does not know and
|
|
150
|
+
* names it); a second, silent bound in the writers is not.
|
|
151
|
+
*
|
|
152
|
+
* @param {object} meta - A resolved `site.sections` / `site.readmeSections`
|
|
153
|
+
* entry.
|
|
154
|
+
* @returns {object} Its front matter, `title` first.
|
|
155
|
+
*/
|
|
156
|
+
export function sectionFrontmatter(meta: object): object;
|
|
132
157
|
/**
|
|
133
158
|
* The frontmatter a page publishes with.
|
|
134
159
|
*
|
|
@@ -23,6 +23,30 @@
|
|
|
23
23
|
* @returns {ParsedWikilink} The parts, each trimmed.
|
|
24
24
|
*/
|
|
25
25
|
export function parseWikilink(rawInner: string): ParsedWikilink;
|
|
26
|
+
/**
|
|
27
|
+
* The label an author actually supplied, or `null` when they supplied none.
|
|
28
|
+
*
|
|
29
|
+
* **An empty label is not a label.** `[[x|]]` is deliberately writable — it
|
|
30
|
+
* means "address this target, and show the target's own name" — so `display:
|
|
31
|
+
* ""` has to read as *absent* everywhere a fallback is chosen, exactly as
|
|
32
|
+
* `display: null` does. The two are still distinguishable through
|
|
33
|
+
* {@link ParsedWikilink.labelled}, which is the thing that genuinely differs
|
|
34
|
+
* and which #1409 depends on.
|
|
35
|
+
*
|
|
36
|
+
* Stated here because the two resolvers had already drawn the line in two
|
|
37
|
+
* places and drawn it differently: the packs tested falsiness and were right,
|
|
38
|
+
* the web tested `??` — which falls through on `null` only — and emitted
|
|
39
|
+
* `[](/url/)`, a link with no clickable text, through every build (#113). That
|
|
40
|
+
* is the same drift this module exists to prevent, in the case its own
|
|
41
|
+
* {@link ParsedWikilink} docstring calls out. One reading, one place.
|
|
42
|
+
*
|
|
43
|
+
* @param {{display: string|null}} parsed - A parsed wikilink, or anything
|
|
44
|
+
* carrying its `display`.
|
|
45
|
+
* @returns {string|null} The label, or `null` when there is none to show.
|
|
46
|
+
*/
|
|
47
|
+
export function authoredLabel({ display }: {
|
|
48
|
+
display: string | null;
|
|
49
|
+
}): string | null;
|
|
26
50
|
/**
|
|
27
51
|
* Whether a parsed link addresses a section of the page it is written on.
|
|
28
52
|
*
|
package/types/sohl/actors.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class Actors extends BasePackCompiler {
|
|
2
2
|
constructor({ itemsSourceDirs, foreignSourceDirs, ...options }: {
|
|
3
3
|
[x: string]: any;
|
|
4
|
-
itemsSourceDirs
|
|
4
|
+
itemsSourceDirs?: never[] | undefined;
|
|
5
5
|
foreignSourceDirs?: never[] | undefined;
|
|
6
6
|
});
|
|
7
7
|
/** @type {readonly string[]} */
|
|
@@ -60,8 +60,8 @@ export class Actors extends BasePackCompiler {
|
|
|
60
60
|
system: {
|
|
61
61
|
shortcode: any;
|
|
62
62
|
portrait: string;
|
|
63
|
-
appearance:
|
|
64
|
-
dossier:
|
|
63
|
+
appearance: string;
|
|
64
|
+
dossier: string;
|
|
65
65
|
};
|
|
66
66
|
items: any[];
|
|
67
67
|
prototypeToken: {
|