@heroiclands/package-build 0.6.1 → 3.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-content-build.md +965 -0
- package/CHANGELOG.md +58 -0
- package/CONTENT.md +824 -0
- package/MIGRATING.md +127 -0
- package/README.md +29 -17
- package/bin/content-build.mjs +1082 -0
- package/bin/package-build.mjs +2 -2
- package/bin/report.mjs +2 -2
- package/config.mjs +21 -18
- package/content-config.mjs +1424 -0
- package/coverage.mjs +1 -1
- package/e2e.mjs +1 -1
- package/engine/abbreviations.mjs +0 -0
- package/engine/base-compiler.mjs +608 -0
- package/engine/code-fences.mjs +311 -0
- package/engine/compendiums.mjs +353 -0
- package/engine/content-address.mjs +141 -0
- package/engine/content-links.mjs +438 -0
- package/engine/content-lint.mjs +204 -0
- package/engine/content-package.mjs +67 -0
- package/engine/content-slug.mjs +169 -0
- package/engine/content-tables.mjs +1216 -0
- package/engine/content-tree.mjs +56 -0
- package/engine/diagnostics.mjs +270 -0
- package/engine/field-reference.mjs +242 -0
- package/engine/field-spec.mjs +218 -0
- package/engine/foreign-catalog.mjs +450 -0
- package/engine/foreign-manifests.mjs +129 -0
- package/engine/frontmatter-lint.mjs +343 -0
- package/engine/frontmatter.mjs +258 -0
- package/engine/generate.mjs +305 -0
- package/engine/helpers.mjs +809 -0
- package/engine/ids.mjs +192 -0
- package/engine/index.mjs +142 -0
- package/engine/item-docs.mjs +153 -0
- package/engine/item-registry.mjs +129 -0
- package/engine/journals.mjs +374 -0
- package/engine/kb-manifest.mjs +473 -0
- package/engine/macros.mjs +333 -0
- package/engine/manifest-emit.mjs +339 -0
- package/engine/map-notes.mjs +1302 -0
- package/engine/pack-config.mjs +479 -0
- package/engine/pack-router.mjs +254 -0
- package/engine/prose-config.mjs +238 -0
- package/engine/prose-lint.mjs +294 -0
- package/engine/region-events.mjs +79 -0
- package/engine/scene-levels.mjs +420 -0
- package/engine/scenes.mjs +528 -0
- package/engine/site-build.mjs +778 -0
- package/engine/site-index.mjs +332 -0
- package/engine/web-wikilinks.mjs +330 -0
- package/engine/wikilink-syntax.mjs +105 -0
- package/engine/wikilinks.mjs +645 -0
- package/index.mjs +42 -25
- package/lang.mjs +1 -1
- package/manifest.mjs +79 -5
- package/markdownlint-config.mjs +45 -0
- package/package.json +78 -35
- package/prettier-config.mjs +34 -0
- package/sohl/actors.mjs +581 -0
- package/sohl/affiliation-standings.mjs +44 -0
- package/sohl/being-info.mjs +173 -0
- package/sohl/default-item-art.mjs +82 -0
- package/sohl/index.mjs +55 -0
- package/sohl/item-builders.mjs +114 -0
- package/sohl/item-fields.mjs +867 -0
- package/sohl/items.mjs +197 -0
- package/sohl/kb-passes.mjs +207 -0
- package/sohl/note-schemas.mjs +322 -0
- package/templates.mjs +1 -1
- package/types/config.d.mts +1 -1
- package/types/content-config.d.mts +571 -0
- package/types/engine/abbreviations.d.mts +44 -0
- package/types/engine/base-compiler.d.mts +320 -0
- package/types/engine/code-fences.d.mts +102 -0
- package/types/engine/compendiums.d.mts +77 -0
- package/types/engine/content-address.d.mts +61 -0
- package/types/engine/content-links.d.mts +87 -0
- package/types/engine/content-lint.d.mts +47 -0
- package/types/engine/content-package.d.mts +36 -0
- package/types/engine/content-slug.d.mts +59 -0
- package/types/engine/content-tables.d.mts +131 -0
- package/types/engine/content-tree.d.mts +12 -0
- package/types/engine/diagnostics.d.mts +152 -0
- package/types/engine/field-reference.d.mts +20 -0
- package/types/engine/field-spec.d.mts +181 -0
- package/types/engine/foreign-catalog.d.mts +103 -0
- package/types/engine/foreign-manifests.d.mts +43 -0
- package/types/engine/frontmatter-lint.d.mts +74 -0
- package/types/engine/frontmatter.d.mts +99 -0
- package/types/engine/generate.d.mts +56 -0
- package/types/engine/helpers.d.mts +348 -0
- package/types/engine/ids.d.mts +126 -0
- package/types/engine/index.d.mts +33 -0
- package/types/engine/item-docs.d.mts +73 -0
- package/types/engine/item-registry.d.mts +57 -0
- package/types/engine/journals.d.mts +121 -0
- package/types/engine/kb-manifest.d.mts +214 -0
- package/types/engine/macros.d.mts +201 -0
- package/types/engine/manifest-emit.d.mts +137 -0
- package/types/engine/map-notes.d.mts +283 -0
- package/types/engine/pack-config.d.mts +77 -0
- package/types/engine/pack-router.d.mts +53 -0
- package/types/engine/prose-config.d.mts +121 -0
- package/types/engine/prose-lint.d.mts +59 -0
- package/types/engine/region-events.d.mts +43 -0
- package/types/engine/scene-levels.d.mts +36 -0
- package/types/engine/scenes.d.mts +48 -0
- package/types/engine/site-build.d.mts +214 -0
- package/types/engine/site-index.d.mts +141 -0
- package/types/engine/web-wikilinks.d.mts +67 -0
- package/types/engine/wikilink-syntax.d.mts +101 -0
- package/types/engine/wikilinks.d.mts +172 -0
- package/types/index.d.mts +3 -0
- package/types/manifest.d.mts +52 -4
- package/types/markdownlint-config.d.mts +8 -0
- package/types/prettier-config.d.mts +1 -0
- package/types/sohl/actors.d.mts +65 -0
- package/types/sohl/affiliation-standings.d.mts +22 -0
- package/types/sohl/being-info.d.mts +78 -0
- package/types/sohl/default-item-art.d.mts +28 -0
- package/types/sohl/index.d.mts +7 -0
- package/types/sohl/item-builders.d.mts +11 -0
- package/types/sohl/item-fields.d.mts +20 -0
- package/types/sohl/items.d.mts +19 -0
- package/types/sohl/kb-passes.d.mts +71 -0
- package/types/sohl/note-schemas.d.mts +7 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every SoHL action name this build knows about, for the `action:` warning on a
|
|
3
|
+
* region trigger.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately a **superset**, gathered from the localization keys every
|
|
6
|
+
* intrinsic action's `title:` points at and from the `shortcode:` / `executor:`
|
|
7
|
+
* string literals the action definitions carry. A warning that fires on a real
|
|
8
|
+
* action would be worse than one that misses a typo, so the wider net is the
|
|
9
|
+
* right one: this only has to recognise the names that exist, not enumerate
|
|
10
|
+
* them exactly.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} repoRoot - The repository root.
|
|
13
|
+
* @returns {Set<string>} The known action names.
|
|
14
|
+
*/
|
|
15
|
+
export function collectKnownActionNames(repoRoot: string): Set<string>;
|
|
16
|
+
export class Scenes extends BasePackCompiler {
|
|
17
|
+
constructor({ contentBase, dest, companionDests, folderResolver, repoRoot, }: {
|
|
18
|
+
contentBase: any;
|
|
19
|
+
dest: any;
|
|
20
|
+
companionDests?: {} | undefined;
|
|
21
|
+
folderResolver?: (() => null) | undefined;
|
|
22
|
+
repoRoot?: string | undefined;
|
|
23
|
+
});
|
|
24
|
+
/** @type {string} */
|
|
25
|
+
adventureDir: string;
|
|
26
|
+
/**
|
|
27
|
+
* Adventures this pass bundled, for the summary.
|
|
28
|
+
*
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
adventureCount: number;
|
|
32
|
+
index: Map<string, object> | undefined;
|
|
33
|
+
effectsByAddress: Map<string, object> | undefined;
|
|
34
|
+
knownActions: Set<string> | undefined;
|
|
35
|
+
/** place key → `{name, img, scenes: [], journal: []}` */
|
|
36
|
+
places: Map<any, any> | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Bundle each pinned place into an Adventure, once every map of it has
|
|
39
|
+
* compiled.
|
|
40
|
+
*
|
|
41
|
+
* @returns {Promise<void>}
|
|
42
|
+
*/
|
|
43
|
+
finish(): Promise<void>;
|
|
44
|
+
/** @inheritdoc */
|
|
45
|
+
reportCompiled(stats: any): void;
|
|
46
|
+
#private;
|
|
47
|
+
}
|
|
48
|
+
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `.md` file under `dir`, depth-first in directory order.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately *not* {@link walkMarkdownTree}, whose stack-based walk yields a
|
|
5
|
+
* tree in reverse. Order is load-bearing here and nowhere else: the address
|
|
6
|
+
* index resolves a bare `[[Name]]` on a first-writer-wins basis, so reversing
|
|
7
|
+
* the walk silently changes which page an ambiguous name resolves to. A pack
|
|
8
|
+
* compile has no such dependency, which is why the two walks can differ.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} dir - Directory to walk.
|
|
11
|
+
* @param {readonly string[]} skip - Directory names to ignore at any depth.
|
|
12
|
+
* @returns {string[]} Absolute paths.
|
|
13
|
+
*/
|
|
14
|
+
export function walkSiteTree(dir: string, skip?: readonly string[]): string[];
|
|
15
|
+
/**
|
|
16
|
+
* The content tree's pages, and what could not be addressed.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} contentBase - Absolute path to the content tree.
|
|
19
|
+
* @param {object} ctx - `{ packages, skipDirectories, mount, scheme }`.
|
|
20
|
+
* @returns {{pages: object[], slugFindings: object[], fmLinkFindings: object[]}}
|
|
21
|
+
*/
|
|
22
|
+
export function collectContentPages(contentBase: string, ctx: object): {
|
|
23
|
+
pages: object[];
|
|
24
|
+
slugFindings: object[];
|
|
25
|
+
fmLinkFindings: object[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* An extra tree's pages — a documentation tree published alongside the content.
|
|
29
|
+
*
|
|
30
|
+
* These preserve their **source layout** below the section rather than being
|
|
31
|
+
* addressed by type and slug: they are a book with chapters, and a reader
|
|
32
|
+
* follows their paths. A `README` is its directory's landing.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} tree - `{ from, rel, section, route }`.
|
|
35
|
+
* @param {object} ctx - `{ mount }`.
|
|
36
|
+
* @returns {{pages: object[], fmLinkFindings: object[]}}
|
|
37
|
+
*/
|
|
38
|
+
export function collectTreePages(tree: object, ctx: object): {
|
|
39
|
+
pages: object[];
|
|
40
|
+
fmLinkFindings: object[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The integrity gates a site build runs before it writes anything.
|
|
44
|
+
*
|
|
45
|
+
* Every one of these was an inline `process.exit` in both consumer scripts, so
|
|
46
|
+
* none of them had a test. They are grouped here, reporting rather than exiting,
|
|
47
|
+
* because the order matters and the reasons are worth stating once:
|
|
48
|
+
*
|
|
49
|
+
* - **Frontmatter wikilinks** first, because frontmatter is copied to the page
|
|
50
|
+
* verbatim and a link written in one reaches the reader as literal `[[…]]`.
|
|
51
|
+
* - **Slugs and collisions** next: a note that derives no URL, or two that
|
|
52
|
+
* derive the same one, would silently drop or overwrite a page.
|
|
53
|
+
* - **Foreign manifests** last, in two steps. *Unusable* is a file this build
|
|
54
|
+
* cannot read; *unaddressable* is one it can read but cannot look anything up
|
|
55
|
+
* in — a distinction worth keeping, because the second surfaces as a pile of
|
|
56
|
+
* dead links blaming the notes that cite them rather than the file at fault.
|
|
57
|
+
*
|
|
58
|
+
* @param {object[]} pages - Every page, from both walks.
|
|
59
|
+
* @param {object} findings - `{ slugFindings, fmLinkFindings }` from collection.
|
|
60
|
+
* @param {object} options - `{ manifestDir }`.
|
|
61
|
+
* @returns {object} The gate results and, when they pass, the built index.
|
|
62
|
+
*/
|
|
63
|
+
export function siteGates(pages: object[], findings: object, { manifestDir }: object): object;
|
|
64
|
+
/** Whether any gate produced a finding. */
|
|
65
|
+
export function gatesFailed(gates: any): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The universe a generated table searches, grouped by package.
|
|
68
|
+
*
|
|
69
|
+
* Reference pages only, and grouped so a page never tabulates another package's
|
|
70
|
+
* content: a table is a claim about what this package ships.
|
|
71
|
+
*
|
|
72
|
+
* @param {object[]} pages - Every page.
|
|
73
|
+
* @returns {Map<string, object[]>} Package → the notes it may tabulate.
|
|
74
|
+
*/
|
|
75
|
+
export function tableUniverse(pages: object[]): Map<string, object[]>;
|
|
76
|
+
/**
|
|
77
|
+
* The frontmatter a page publishes with.
|
|
78
|
+
*
|
|
79
|
+
* An authored `aliases` is Obsidian's — a list of *names* a reader might call
|
|
80
|
+
* the note, which is vault addressing and stays in the vault. Hugo reads
|
|
81
|
+
* `aliases` as **URL redirects**, so passing them through would publish a
|
|
82
|
+
* redirect stub at each name. They are dropped, and this build emits no
|
|
83
|
+
* redirects of its own.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} page - The page.
|
|
86
|
+
* @param {object} options - `{ sections, readmeSections, decorate }`.
|
|
87
|
+
* @returns {object} The frontmatter to write.
|
|
88
|
+
*/
|
|
89
|
+
export function pageFrontmatter(page: object, { readmeSections, decorate }: object): object;
|
|
90
|
+
/** Where a page is written, relative to the output root. */
|
|
91
|
+
export function pageDestination(page: any): string;
|
|
92
|
+
/**
|
|
93
|
+
* Renders and writes every page.
|
|
94
|
+
*
|
|
95
|
+
* The order inside a page is load-bearing and is the same order the pack
|
|
96
|
+
* compilers use:
|
|
97
|
+
*
|
|
98
|
+
* 1. **Tables expand first**, and outside code-fence protection. A table is
|
|
99
|
+
* authored as a fenced `dataview` block, which `protectCode` would otherwise
|
|
100
|
+
* stash away before the expander saw it. Expanding first leaves an ordinary
|
|
101
|
+
* markdown table to walk, with every other fence still protected.
|
|
102
|
+
* 2. **Then, inside protection**: the consumer's `beforeLinks` pass, wikilink
|
|
103
|
+
* resolution, and the consumer's `afterLinks` pass. A `{@link}` tag may sit
|
|
104
|
+
* in prose a wikilink also touches, so the repository's own rewrites bracket
|
|
105
|
+
* the shared one rather than replacing it.
|
|
106
|
+
*
|
|
107
|
+
* @param {object[]} pages - Every page.
|
|
108
|
+
* @param {object} options - Everything the render needs.
|
|
109
|
+
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}}
|
|
110
|
+
*/
|
|
111
|
+
export function renderPages(pages: object[], options: object): {
|
|
112
|
+
written: number;
|
|
113
|
+
byKind: Record<string, number>;
|
|
114
|
+
tableErrors: object[];
|
|
115
|
+
wikiErrors: object[];
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Writes the section landings a published tree needs but no note supplies.
|
|
119
|
+
*
|
|
120
|
+
* Two separate jobs, and both exist because of how Hugo decides what a section
|
|
121
|
+
* is:
|
|
122
|
+
*
|
|
123
|
+
* - **Declared sections** get a titled `_index.md` with their hero, so a landing
|
|
124
|
+
* matches the card that links to it instead of showing Hugo's auto-humanised
|
|
125
|
+
* directory name. The body is empty, which lets the theme list the section's
|
|
126
|
+
* children — or say it is empty, for a section whose content has not shipped.
|
|
127
|
+
* - **Every other section directly under the mount** gets a bare `_index.md`,
|
|
128
|
+
* or its own address publishes nothing. Hugo generates a section page
|
|
129
|
+
* automatically only for a *top-level* content directory; below that, a
|
|
130
|
+
* directory without an `_index.md` is not a section, so its URL 404s while
|
|
131
|
+
* its children publish normally. Mounting a tree one level down demotes every
|
|
132
|
+
* section it holds, and the ones with no landing of their own quietly stop
|
|
133
|
+
* existing while every page inside them keeps working.
|
|
134
|
+
*
|
|
135
|
+
* Scoped to one level on purpose. A directory further down was not a section
|
|
136
|
+
* before the move either, and giving it one here would silently re-scope the
|
|
137
|
+
* prev/next navigation of every page inside it.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} outRoot - The mount directory.
|
|
140
|
+
* @param {object} options - `{ sections, landing, sectionTitle }`.
|
|
141
|
+
* @returns {number} How many landings were written.
|
|
142
|
+
*/
|
|
143
|
+
export function writeSectionLandings(outRoot: string, { sections, landing, sectionTitle }: object): number;
|
|
144
|
+
/**
|
|
145
|
+
* A section landing's title, from its directory name — `macro` → `Macros`.
|
|
146
|
+
*
|
|
147
|
+
* Hugo derives exactly this for a section page it generates itself, but not for
|
|
148
|
+
* one backed by an `_index.md`: an explicit file with no `title` renders a blank
|
|
149
|
+
* heading. So a backfilled landing states its own, in plain English
|
|
150
|
+
* pluralisation rather than Hugo's inflector, which spells that section
|
|
151
|
+
* "Macroes".
|
|
152
|
+
*
|
|
153
|
+
* @param {string} name - The directory name.
|
|
154
|
+
* @returns {string} The display title.
|
|
155
|
+
*/
|
|
156
|
+
export function pluralTitle(name: string): string;
|
|
157
|
+
/**
|
|
158
|
+
* Resolves `site.pass` to its bundle.
|
|
159
|
+
*
|
|
160
|
+
* @param {string|undefined} name - The configured name.
|
|
161
|
+
* @param {object} options - The configured options, plus `repoRoot`.
|
|
162
|
+
* @returns {{beforeLinks?: Function, afterLinks?: Function}} The bundle.
|
|
163
|
+
*/
|
|
164
|
+
export function resolveSitePass(name: string | undefined, options: object): {
|
|
165
|
+
beforeLinks?: Function;
|
|
166
|
+
afterLinks?: Function;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* The output root, having established that it is safe to delete.
|
|
170
|
+
*
|
|
171
|
+
* The whole tree is a build artifact and is wiped on every run, so this
|
|
172
|
+
* resolution is the difference between clearing a build directory and clearing
|
|
173
|
+
* the repository. An unset `site.out` resolves to `rootDir` itself, and the
|
|
174
|
+
* wipe then deletes the working tree — which is not a hypothetical: it happened
|
|
175
|
+
* while this module was being written, on a configuration that simply had no
|
|
176
|
+
* `site` section yet.
|
|
177
|
+
*
|
|
178
|
+
* So the path is refused unless it is **strictly inside** the repository root.
|
|
179
|
+
* Both failing shapes are ordinary rather than exotic — an absent setting, and a
|
|
180
|
+
* `..` that climbs out — and neither should be recoverable by being careful.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} rootDir - The repository root.
|
|
183
|
+
* @param {string} out - The configured `site.out`.
|
|
184
|
+
* @returns {string} The absolute output root.
|
|
185
|
+
* @throws {Error} When it is unset, or is not below `rootDir`.
|
|
186
|
+
*/
|
|
187
|
+
export function resolveOutputRoot(rootDir: string, out: string): string;
|
|
188
|
+
/**
|
|
189
|
+
* Builds a Hugo content tree from a content tree, and reports what it found.
|
|
190
|
+
*
|
|
191
|
+
* Returns rather than exits, in every case. A caller — the command, or a test —
|
|
192
|
+
* decides what a finding means; the gates below are grouped so it can report
|
|
193
|
+
* the first that fired and stop, which is what keeps a wall of dead links from
|
|
194
|
+
* burying the one manifest that caused them.
|
|
195
|
+
*
|
|
196
|
+
* @param {object} [options] - Options.
|
|
197
|
+
* @param {object} [options.config] - A resolved configuration; loaded when
|
|
198
|
+
* omitted.
|
|
199
|
+
* @param {string} [options.outRoot] - Override the configured output mount.
|
|
200
|
+
* @returns {{gates: object, stats: object|null, tableErrors: object[],
|
|
201
|
+
* wikiErrors: object[], manifests: object|null}}
|
|
202
|
+
*/
|
|
203
|
+
export function buildSite({ config, outRoot }?: {
|
|
204
|
+
config?: object | undefined;
|
|
205
|
+
outRoot?: string | undefined;
|
|
206
|
+
}): {
|
|
207
|
+
gates: object;
|
|
208
|
+
stats: object | null;
|
|
209
|
+
tableErrors: object[];
|
|
210
|
+
wikiErrors: object[];
|
|
211
|
+
manifests: object | null;
|
|
212
|
+
};
|
|
213
|
+
export { formatUnaddressableFinding };
|
|
214
|
+
import { formatUnaddressableFinding } from "./foreign-manifests.mjs";
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the address index a site's wikilink resolver reads.
|
|
3
|
+
*
|
|
4
|
+
* @param {readonly SiteEntry[]} entries - Every page the site will publish,
|
|
5
|
+
* each already knowing its own `url`.
|
|
6
|
+
* @param {object} [options] - Cross-package inputs.
|
|
7
|
+
* @param {Map<string, {package: string, type?: string}>} [options.foreignIndex]
|
|
8
|
+
* The merged index from `loadForeignManifests`. Omit when the build publishes
|
|
9
|
+
* no cross-package links.
|
|
10
|
+
* @returns {SiteIndex} The index, and what could not be addressed unambiguously.
|
|
11
|
+
*/
|
|
12
|
+
export function buildSiteIndex(entries: readonly SiteEntry[], { foreignIndex }?: {
|
|
13
|
+
foreignIndex?: Map<string, {
|
|
14
|
+
package: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
}> | undefined;
|
|
17
|
+
}): SiteIndex;
|
|
18
|
+
/**
|
|
19
|
+
* The per-page context a wikilink resolver takes.
|
|
20
|
+
*
|
|
21
|
+
* Assembled here so a consumer spells out only what is genuinely its own — the
|
|
22
|
+
* source path, the citing note's type, and where errors collect — instead of
|
|
23
|
+
* restating the whole index every call. Both site builds wrote this object by
|
|
24
|
+
* hand, identically.
|
|
25
|
+
*
|
|
26
|
+
* @param {SiteIndex} built - The result of {@link buildSiteIndex}.
|
|
27
|
+
* @param {object} options - Per-page inputs.
|
|
28
|
+
* @param {string} options.src - Source path of the page being resolved, for
|
|
29
|
+
* diagnostics.
|
|
30
|
+
* @param {string|null} [options.type] - The citing note's type, which scopes a
|
|
31
|
+
* bare alias lookup.
|
|
32
|
+
* @param {object[]} options.errors - Collector the resolver appends to.
|
|
33
|
+
* @param {Map<string, object>} [options.foreignIndex] - The foreign index, for
|
|
34
|
+
* resolvers that distinguish a foreign hit from a local one.
|
|
35
|
+
* @param {boolean} [options.manifestsComplete] - Whether every package this
|
|
36
|
+
* build links into supplied a manifest. When false, a resolver may soften an
|
|
37
|
+
* unresolved cross-package link rather than fail.
|
|
38
|
+
* @returns {object} The resolver context.
|
|
39
|
+
*/
|
|
40
|
+
export function wikiContext(built: SiteIndex, { src, type, errors, foreignIndex, manifestsComplete, }: {
|
|
41
|
+
src: string;
|
|
42
|
+
type?: string | null | undefined;
|
|
43
|
+
errors: object[];
|
|
44
|
+
foreignIndex?: Map<string, object> | undefined;
|
|
45
|
+
manifestsComplete?: boolean | undefined;
|
|
46
|
+
}): object;
|
|
47
|
+
/**
|
|
48
|
+
* One page the site will publish, as the index needs to see it.
|
|
49
|
+
*/
|
|
50
|
+
export type SiteEntry = {
|
|
51
|
+
/**
|
|
52
|
+
* `"content"` for a note compiled from the content
|
|
53
|
+
* tree, anything else for a page that carries no
|
|
54
|
+
* `type`/`shortcode` (a developer doc, say). Only
|
|
55
|
+
* content entries take part in type-scoped indexing.
|
|
56
|
+
*/
|
|
57
|
+
kind: string;
|
|
58
|
+
/**
|
|
59
|
+
* The note's frontmatter.
|
|
60
|
+
*/
|
|
61
|
+
fm: object;
|
|
62
|
+
/**
|
|
63
|
+
* Display name.
|
|
64
|
+
*/
|
|
65
|
+
name: string;
|
|
66
|
+
/**
|
|
67
|
+
* URL segment.
|
|
68
|
+
*/
|
|
69
|
+
slug: string;
|
|
70
|
+
/**
|
|
71
|
+
* Section the page is filed under.
|
|
72
|
+
*/
|
|
73
|
+
sec: string;
|
|
74
|
+
/**
|
|
75
|
+
* Source file's basename, e.g. `Climbing.md`.
|
|
76
|
+
*/
|
|
77
|
+
base: string;
|
|
78
|
+
/**
|
|
79
|
+
* The page's published address.
|
|
80
|
+
*/
|
|
81
|
+
url: string;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the page is its section's landing.
|
|
84
|
+
*/
|
|
85
|
+
isReadme: boolean;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* The resolved index and everything a wikilink resolver reads beside it.
|
|
89
|
+
*/
|
|
90
|
+
export type SiteIndex = {
|
|
91
|
+
/**
|
|
92
|
+
* Address → page.
|
|
93
|
+
*/
|
|
94
|
+
index: Map<string, {
|
|
95
|
+
url: string;
|
|
96
|
+
name?: string;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* Keys claimed by two pages, and so
|
|
100
|
+
* deliberately absent from `index`.
|
|
101
|
+
*/
|
|
102
|
+
ambiguous: Set<string>;
|
|
103
|
+
/**
|
|
104
|
+
* `type|alias`.
|
|
105
|
+
*/
|
|
106
|
+
typeAlias: Map<string, {
|
|
107
|
+
url: string;
|
|
108
|
+
name?: string;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Type-scoped aliases claimed twice.
|
|
112
|
+
*/
|
|
113
|
+
typeCollide: Set<string>;
|
|
114
|
+
/**
|
|
115
|
+
* Every type the resolver should read as
|
|
116
|
+
* an address qualifier, local and foreign.
|
|
117
|
+
*/
|
|
118
|
+
contentTypes: Set<string>;
|
|
119
|
+
/**
|
|
120
|
+
* Section names, lowercased.
|
|
121
|
+
*/
|
|
122
|
+
sections: Set<string>;
|
|
123
|
+
/**
|
|
124
|
+
* `type:shortcode`
|
|
125
|
+
* → page, for callers resolving embedded
|
|
126
|
+
* references (a being's items, say).
|
|
127
|
+
*/
|
|
128
|
+
refIndex: Map<string, {
|
|
129
|
+
name: string;
|
|
130
|
+
url: string;
|
|
131
|
+
}>;
|
|
132
|
+
/**
|
|
133
|
+
* Addresses claimed by
|
|
134
|
+
* more than one package. Non-empty is a
|
|
135
|
+
* build failure; the caller reports it.
|
|
136
|
+
*/
|
|
137
|
+
conflicts: {
|
|
138
|
+
key: string;
|
|
139
|
+
package: string;
|
|
140
|
+
}[];
|
|
141
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every wikilink authored inside a frontmatter value (#1428).
|
|
3
|
+
*
|
|
4
|
+
* Wikilinks are resolved in a note's **body** — by {@link resolveWebWikilinks}
|
|
5
|
+
* here, and by the pack compilers' `convertWikilinks` for Foundry. Frontmatter
|
|
6
|
+
* is not markdown and is never walked by either, so a link written in one is
|
|
7
|
+
* copied through verbatim and reaches the reader as literal `[[…]]` text, in
|
|
8
|
+
* whatever the theme renders that field as (an infobox row, a description, a
|
|
9
|
+
* card subtitle). Nothing downstream notices: the value is a valid string, the
|
|
10
|
+
* page builds, and the defect is visible only to someone who looks at it.
|
|
11
|
+
*
|
|
12
|
+
* So the form is refused rather than resolved. Resolving it would mean choosing
|
|
13
|
+
* an output syntax for a field whose renderer is unknown to this build — a
|
|
14
|
+
* markdown link is inert in a Hugo template that prints the value as text, and
|
|
15
|
+
* an `<a>` is unusable in one that escapes it — and would quietly bless an
|
|
16
|
+
* authoring habit that the pack build has no way to honour at all. Frontmatter
|
|
17
|
+
* carries data; a link belongs in prose.
|
|
18
|
+
*
|
|
19
|
+
* Values are read from the *parsed* frontmatter, so a `[[` inside a YAML comment
|
|
20
|
+
* is not a hit, and every hit can be named by the path a reader would look at.
|
|
21
|
+
*
|
|
22
|
+
* @param {unknown} fm - Parsed frontmatter, as `gray-matter` returns it.
|
|
23
|
+
* @returns {Array<{path: string, link: string}>} In reading order; `path` is the
|
|
24
|
+
* dotted key path of the offending value (`government.summary`, `aliases.1`).
|
|
25
|
+
*/
|
|
26
|
+
export function frontmatterWikilinks(fm: unknown): Array<{
|
|
27
|
+
path: string;
|
|
28
|
+
link: string;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Rewrites the wikilinks in a markdown body as KB-local markdown links.
|
|
32
|
+
*
|
|
33
|
+
* A target is looked up case-insensitively: first as an alias scoped to the
|
|
34
|
+
* source's own **type** (`ctx.typeAlias`, keyed `type|alias`) — a note's
|
|
35
|
+
* directory and `category` play no part — then in the KB-wide `ctx.index` (keyed
|
|
36
|
+
* by the unambiguous `section/slug` and `type/shortcode`, plus name/filename/slug
|
|
37
|
+
* fallbacks).
|
|
38
|
+
*
|
|
39
|
+
* An unresolved target fails the build only when it is a genuine intra-KB
|
|
40
|
+
* problem — an ambiguous alias, or a qualified `prefix/key` whose prefix is a
|
|
41
|
+
* real KB section or content directory. Anything else is treated as an external
|
|
42
|
+
* reference — until every package's manifest is present, after which any
|
|
43
|
+
* `type-shortcode` address resolving nowhere fails too. Failures are collected
|
|
44
|
+
* in `ctx.errors`.
|
|
45
|
+
*
|
|
46
|
+
* Whether or not it fails the build, a target that resolves nowhere renders
|
|
47
|
+
* through {@link unresolvedLink} rather than as bare prose (#1665): the author's
|
|
48
|
+
* text is kept, marked so a reader can see a link was intended. Not failing the
|
|
49
|
+
* build is a statement that the link *may* be legitimate prose — it was never a
|
|
50
|
+
* reason to make a dead link indistinguishable from the sentence around it.
|
|
51
|
+
*
|
|
52
|
+
* A target that **resolved** to an entry with no page is not this case and is
|
|
53
|
+
* not marked: a pack-only package (#1516) publishes Foundry addresses and no
|
|
54
|
+
* web pages, so the author wrote a real address and there is simply nothing to
|
|
55
|
+
* link to.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} body - The markdown body.
|
|
58
|
+
* @param {object} ctx - `{ index, typeAlias, collide, typeCollide, sections,
|
|
59
|
+
* contentTypes, foreign, manifestsComplete, type, errors, src }`. `foreign`
|
|
60
|
+
* is the cross-package manifest index (#1446); `manifestsComplete` says
|
|
61
|
+
* whether every linkable package is accounted for. Together they decide
|
|
62
|
+
* whether an unresolved address is a typo or a package merely absent.
|
|
63
|
+
* @returns {string} The body with wikilinks rewritten.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveWebWikilinks(body: string, ctx: object): string;
|
|
66
|
+
export { slugify };
|
|
67
|
+
import { slugify } from "./content-slug.mjs";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of a wikilink's interior.
|
|
3
|
+
*
|
|
4
|
+
* @typedef {object} ParsedWikilink
|
|
5
|
+
* @property {string} inner - The whole interior, unescaped and trimmed. What an
|
|
6
|
+
* unlabelled link displays, anchor and all.
|
|
7
|
+
* @property {string} target - What is linked to: an address, an alias, or `""`
|
|
8
|
+
* for a link to a section of the same page.
|
|
9
|
+
* @property {string} anchor - The `#section` slug, `""` when there is none.
|
|
10
|
+
* @property {string|null} display - The text after `|`, or `null` when the link
|
|
11
|
+
* is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
|
|
12
|
+
* @property {boolean} labelled - Whether a `|` was present at all. What an
|
|
13
|
+
* unlabelled link shows depends on whether its target read as an address
|
|
14
|
+
* (SoHL#1409), so the caller needs to know.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Split a wikilink's interior into its parts.
|
|
18
|
+
*
|
|
19
|
+
* Takes the **inside** of the brackets — the capture group of {@link WIKILINK} —
|
|
20
|
+
* not the whole link, so a caller that has already matched does not re-match.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} rawInner - The text between `[[` and `]]`.
|
|
23
|
+
* @returns {ParsedWikilink} The parts, each trimmed.
|
|
24
|
+
*/
|
|
25
|
+
export function parseWikilink(rawInner: string): ParsedWikilink;
|
|
26
|
+
/**
|
|
27
|
+
* Whether a parsed link addresses a section of the page it is written on.
|
|
28
|
+
*
|
|
29
|
+
* `[[#some-heading]]` — no target, only an anchor. Both resolvers special-case
|
|
30
|
+
* it before consulting any index, because there is nothing to look up.
|
|
31
|
+
*
|
|
32
|
+
* @param {ParsedWikilink} parsed - A parsed wikilink.
|
|
33
|
+
* @returns {boolean} True when the link is same-page.
|
|
34
|
+
*/
|
|
35
|
+
export function isSamePage({ target, anchor }: ParsedWikilink): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* What a `[[…]]` **is**, before anything decides where it points.
|
|
38
|
+
*
|
|
39
|
+
* One authored link compiles to two different addresses — a Foundry `@UUID`
|
|
40
|
+
* enricher for the packs, a URL for the web — and those two destinations are the
|
|
41
|
+
* *only* thing that legitimately differs. The syntax is the author's, and it is
|
|
42
|
+
* the same syntax whichever build is reading it.
|
|
43
|
+
*
|
|
44
|
+
* It was written twice, and the two copies had already drifted:
|
|
45
|
+
*
|
|
46
|
+
* | Body | `foundry` side | `web` side |
|
|
47
|
+
* | --- | --- | --- |
|
|
48
|
+
* | `[[weapongear-bsw` ⏎ `]]` in a table cell | not a link | a link whose target ends in a newline |
|
|
49
|
+
* | a stray `[[`, a real link two paragraphs later | finds the real link | swallows both paragraphs as one target |
|
|
50
|
+
*
|
|
51
|
+
* The web side's pattern omitted `\n` from the excluded set, so an unclosed
|
|
52
|
+
* bracket consumed everything up to the next `]]` anywhere in the document —
|
|
53
|
+
* the same shape of corruption a hand-rolled code-fence regex caused on the one
|
|
54
|
+
* page whose subject is link syntax (SoHL#1665). It was also internally
|
|
55
|
+
* inconsistent: its *frontmatter* scan excluded newlines while its body scan did
|
|
56
|
+
* not.
|
|
57
|
+
*
|
|
58
|
+
* So the parse lives here, once. A resolver receives the parts and decides only
|
|
59
|
+
* what it is actually for: which address space the target belongs to.
|
|
60
|
+
*
|
|
61
|
+
* @module
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* A wikilink, as authored.
|
|
65
|
+
*
|
|
66
|
+
* **Newlines are excluded deliberately.** A link is written on one line; an
|
|
67
|
+
* unclosed `[[` is a typo, and the alternative is letting it swallow arbitrary
|
|
68
|
+
* prose in search of a closer. Erring towards "not a link" leaves the author's
|
|
69
|
+
* text as written, which is the safe direction for a rewriter.
|
|
70
|
+
*/
|
|
71
|
+
export const WIKILINK: RegExp;
|
|
72
|
+
/**
|
|
73
|
+
* The parts of a wikilink's interior.
|
|
74
|
+
*/
|
|
75
|
+
export type ParsedWikilink = {
|
|
76
|
+
/**
|
|
77
|
+
* - The whole interior, unescaped and trimmed. What an
|
|
78
|
+
* unlabelled link displays, anchor and all.
|
|
79
|
+
*/
|
|
80
|
+
inner: string;
|
|
81
|
+
/**
|
|
82
|
+
* - What is linked to: an address, an alias, or `""`
|
|
83
|
+
* for a link to a section of the same page.
|
|
84
|
+
*/
|
|
85
|
+
target: string;
|
|
86
|
+
/**
|
|
87
|
+
* - The `#section` slug, `""` when there is none.
|
|
88
|
+
*/
|
|
89
|
+
anchor: string;
|
|
90
|
+
/**
|
|
91
|
+
* - The text after `|`, or `null` when the link
|
|
92
|
+
* is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
|
|
93
|
+
*/
|
|
94
|
+
display: string | null;
|
|
95
|
+
/**
|
|
96
|
+
* - Whether a `|` was present at all. What an
|
|
97
|
+
* unlabelled link shows depends on whether its target read as an address
|
|
98
|
+
* (SoHL#1409), so the caller needs to know.
|
|
99
|
+
*/
|
|
100
|
+
labelled: boolean;
|
|
101
|
+
};
|