@heroiclands/package-build 23.7.1 → 23.9.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 +41 -0
- package/README.md +32 -0
- package/bin/content-commands.mjs +32 -10
- package/ci/check-banners.mjs +37 -19
- package/container.mjs +48 -10
- package/docs/api.md +402 -58
- package/docs/authoring/first-note.md +1 -1
- package/docs/authoring/frontmatter.md +30 -1
- package/docs/authoring/links-and-markup.md +154 -56
- package/docs/commands.md +31 -12
- package/docs/content-format.md +25 -2
- package/docs/design.md +31 -0
- package/docs/diagnostics.md +1 -1
- package/docs/guides/e2e.md +37 -1
- package/docs/guides/packs.md +2 -1
- package/docs/guides/reference-pages.md +86 -63
- package/docs/project-setup.md +30 -15
- package/docs/reference/format-details.md +313 -132
- package/docs/reference/note-types.md +191 -174
- package/e2e.mjs +89 -2
- package/engine/anchors.mjs +7 -20
- package/engine/base-compiler.mjs +11 -18
- package/engine/compile-corpus.mjs +5 -3
- package/engine/content-alerts.mjs +4 -2
- package/engine/content-blocks.mjs +18 -36
- package/engine/content-format.yaml +1 -0
- package/engine/content-index.mjs +447 -33
- package/engine/content-links.mjs +43 -15
- package/engine/content-poetry.mjs +9 -0
- package/engine/content-tables.mjs +8 -4
- package/engine/culture-reach.mjs +61 -34
- package/engine/data-keys.mjs +3 -1
- package/engine/diagnostics.mjs +24 -11
- package/engine/dictionary-layout.mjs +151 -19
- package/engine/dictionary-rows.mjs +129 -79
- package/engine/foundry-entries.mjs +21 -3
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/generate.mjs +12 -22
- package/engine/glossary-entries.mjs +11 -11
- package/engine/heading-attributes.mjs +21 -28
- package/engine/helpers.mjs +20 -12
- package/engine/infobox.mjs +3 -0
- package/engine/journals.mjs +15 -2
- package/engine/language-data.mjs +53 -0
- package/engine/language-terms.mjs +86 -0
- package/engine/language-words.mjs +2 -2
- package/engine/lexical-sections.mjs +247 -0
- package/engine/markdownlint-run.mjs +12 -2
- package/engine/markup-validation.mjs +34 -0
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-claims.mjs +21 -10
- package/engine/note-package.mjs +3 -5
- package/engine/note-state.mjs +9 -3
- package/engine/note-vocabulary.mjs +36 -7
- package/engine/pdf-build.mjs +17 -10
- package/engine/prose-lint.mjs +12 -6
- package/engine/related-pages.mjs +22 -3
- package/engine/see-also.mjs +150 -0
- package/engine/site-build.mjs +21 -3
- package/engine/site-preview.mjs +4 -2
- package/engine/sql-tables.mjs +287 -53
- package/engine/standings.mjs +14 -12
- package/engine/stub-lint.mjs +6 -2
- package/engine/web-wikilinks.mjs +8 -6
- package/engine/word-rows.mjs +20 -12
- package/engine/word-terms.mjs +15 -6
- package/hugo-theme/layouts/partials/related.html +25 -8
- package/package.json +2 -1
- package/types/container.d.mts +4 -3
- package/types/e2e.d.mts +28 -0
- package/types/engine/base-compiler.d.mts +2 -2
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-index.d.mts +85 -8
- package/types/engine/content-tables.d.mts +2 -1
- package/types/engine/culture-reach.d.mts +22 -2
- package/types/engine/data-keys.d.mts +7 -0
- package/types/engine/diagnostics.d.mts +7 -6
- package/types/engine/dictionary-layout.d.mts +35 -4
- package/types/engine/dictionary-rows.d.mts +9 -0
- package/types/engine/foundry-entries.d.mts +3 -1
- package/types/engine/heading-attributes.d.mts +2 -2
- package/types/engine/helpers.d.mts +11 -5
- package/types/engine/language-terms.d.mts +7 -0
- package/types/engine/lexical-sections.d.mts +95 -0
- package/types/engine/markdownlint-run.d.mts +3 -1
- package/types/engine/markup-validation.d.mts +14 -0
- package/types/engine/note-addresses.d.mts +10 -0
- package/types/engine/note-claims.d.mts +7 -1
- package/types/engine/note-package.d.mts +3 -1
- package/types/engine/note-state.d.mts +2 -1
- package/types/engine/note-vocabulary.d.mts +1 -0
- package/types/engine/related-pages.d.mts +10 -1
- package/types/engine/see-also.d.mts +55 -0
- package/types/engine/site-build.d.mts +4 -1
- package/types/engine/sql-tables.d.mts +40 -6
- package/types/engine/word-rows.d.mts +9 -7
package/engine/web-wikilinks.mjs
CHANGED
|
@@ -73,12 +73,12 @@ import { resolveEmbeds } from "./content-embeds.mjs";
|
|
|
73
73
|
/**
|
|
74
74
|
* Whether a page's declared anchors include the slug a link names.
|
|
75
75
|
*
|
|
76
|
-
* A local page
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
76
|
+
* A local page carries a `Set` built by {@link module:engine/site-index}.
|
|
77
|
+
* A foreign page declares prose anchors in its note metadata, independently
|
|
78
|
+
* of whether it has a Foundry target map. An empty collection declares none.
|
|
79
|
+
* An absent collection leaves the target unchecked.
|
|
80
80
|
*
|
|
81
|
-
* @param {Set<string>|Record<string, string>|undefined} anchors - The page's
|
|
81
|
+
* @param {Set<string>|Record<string, string>|Array<{slug: string, kind: string}>|undefined} anchors - The page's
|
|
82
82
|
* declared anchors, or `undefined` where none were recorded.
|
|
83
83
|
* @param {string} slug - The anchor a link names.
|
|
84
84
|
* @returns {boolean} Whether the anchor is declared, or `true` when `anchors`
|
|
@@ -87,6 +87,8 @@ import { resolveEmbeds } from "./content-embeds.mjs";
|
|
|
87
87
|
*/
|
|
88
88
|
function hasAnchor(anchors, slug) {
|
|
89
89
|
if (!anchors) return true;
|
|
90
|
+
if (Array.isArray(anchors))
|
|
91
|
+
return anchors.some((anchor) => anchor.kind === "prose" && anchor.slug === slug);
|
|
90
92
|
return anchors instanceof Set ? anchors.has(slug) : Object.hasOwn(anchors, slug);
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -478,7 +480,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
478
480
|
label ?? hit.name ?? target,
|
|
479
481
|
);
|
|
480
482
|
}
|
|
481
|
-
if (anchor && hit.url && !hasAnchor(hit.anchors, anchor)) {
|
|
483
|
+
if (anchor && hit.url && !hasAnchor(hit.noteAnchors ?? hit.anchors, anchor)) {
|
|
482
484
|
return report(
|
|
483
485
|
all,
|
|
484
486
|
{ target, reason: "unknown-anchor", anchor, addressed: true },
|
package/engine/word-rows.mjs
CHANGED
|
@@ -82,15 +82,17 @@ const isMap = (value) => Boolean(value) && typeof value === "object" && !Array.i
|
|
|
82
82
|
const bare = (form) => String(form).replace(/^-+|-+$/g, "");
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* A lookup from a built piece to its gloss, over the words (stems included)
|
|
86
|
-
* and
|
|
85
|
+
* A lookup from a built piece to its gloss, over the words (stems included),
|
|
86
|
+
* affixes and elements of one or more languages.
|
|
87
87
|
*
|
|
88
|
-
*
|
|
89
|
-
* affixes, then the next language's
|
|
90
|
-
*
|
|
91
|
-
*
|
|
88
|
+
* For each spelling, languages are given nearest first: a language's own
|
|
89
|
+
* words, then its affixes and elements, then the next language's. A piece
|
|
90
|
+
* matches an exact spelling across the lineage first, and failing that one
|
|
91
|
+
* written with the same letters and different joining hyphens, so
|
|
92
92
|
* `wulf-` finds the word `wulf`. Within a language a word's own gloss wins over
|
|
93
|
-
* an affix's.
|
|
93
|
+
* an affix's, and both precede an element's sense. Elements also match their
|
|
94
|
+
* declared variants; affixes also match their front-vowel forms. Spelling,
|
|
95
|
+
* including case and accents, is preserved.
|
|
94
96
|
*
|
|
95
97
|
* @param {...object} datas - Each language note's `data`, nearest first.
|
|
96
98
|
* @returns {(piece: string) => string|null} The gloss, or `null` where none resolves.
|
|
@@ -98,18 +100,24 @@ const bare = (form) => String(form).replace(/^-+|-+$/g, "");
|
|
|
98
100
|
export function pieceGlosses(...datas) {
|
|
99
101
|
const exact = new Map();
|
|
100
102
|
const loose = new Map();
|
|
101
|
-
const take = (entries) => {
|
|
103
|
+
const take = (entries, key = "gloss", alternateKey = "") => {
|
|
102
104
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
103
105
|
if (!isMap(entry) || typeof entry.form !== "string") continue;
|
|
104
|
-
const gloss = glossText(entry
|
|
106
|
+
const gloss = glossText(entry[key]);
|
|
105
107
|
if (gloss === null) continue;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
const alternate = entry[alternateKey];
|
|
109
|
+
const forms = [entry.form, ...(Array.isArray(alternate) ? alternate : [alternate])];
|
|
110
|
+
for (const form of forms) {
|
|
111
|
+
if (typeof form !== "string") continue;
|
|
112
|
+
if (!exact.has(form)) exact.set(form, gloss);
|
|
113
|
+
if (!loose.has(bare(form))) loose.set(bare(form), gloss);
|
|
114
|
+
}
|
|
108
115
|
}
|
|
109
116
|
};
|
|
110
117
|
for (const data of datas) {
|
|
111
118
|
take(data?.words);
|
|
112
|
-
take(data?.affixes);
|
|
119
|
+
take(data?.affixes, "gloss", "front");
|
|
120
|
+
take(data?.elements, "sense", "variants");
|
|
113
121
|
}
|
|
114
122
|
return (piece) => exact.get(piece) ?? loose.get(bare(piece)) ?? null;
|
|
115
123
|
}
|
package/engine/word-terms.mjs
CHANGED
|
@@ -136,12 +136,20 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
136
136
|
"A class from the master word classes: a base, then the modifiers the language " +
|
|
137
137
|
"declares in `classModifiers`.",
|
|
138
138
|
}),
|
|
139
|
+
Object.freeze({
|
|
140
|
+
name: "plural",
|
|
141
|
+
kind: "string",
|
|
142
|
+
shape: "a string",
|
|
143
|
+
describe:
|
|
144
|
+
"The noun's explicit plural, where it differs from the language's optional plural " +
|
|
145
|
+
"rule. A dictionary uses this form before the rule; no rule agreement is checked.",
|
|
146
|
+
}),
|
|
139
147
|
Object.freeze({
|
|
140
148
|
name: "field",
|
|
141
149
|
kind: "string",
|
|
142
150
|
required: true,
|
|
143
151
|
shape: "one of the language's `fields` names",
|
|
144
|
-
describe: "The field
|
|
152
|
+
describe: "The field tag beside the dictionary entry, named in the language's `fields`.",
|
|
145
153
|
}),
|
|
146
154
|
Object.freeze({
|
|
147
155
|
name: "gloss",
|
|
@@ -376,27 +384,28 @@ export const GLOSSARY_ENTRY_FIELDS = Object.freeze([
|
|
|
376
384
|
name: "name_sort",
|
|
377
385
|
kind: "string",
|
|
378
386
|
shape: "a string",
|
|
379
|
-
describe: "Its sort key; the name when unstated.",
|
|
387
|
+
describe: "Its sort key; the dictionary word's sort key, or the name, when unstated.",
|
|
380
388
|
}),
|
|
381
389
|
Object.freeze({
|
|
382
390
|
name: "common",
|
|
383
391
|
kind: "boolean",
|
|
384
392
|
shape: "`true` or `false`",
|
|
385
|
-
describe: "
|
|
393
|
+
describe: "Core vocabulary for this glossary's owner; `false` when unstated.",
|
|
386
394
|
}),
|
|
387
395
|
Object.freeze({
|
|
388
396
|
name: "definition",
|
|
389
397
|
kind: "string",
|
|
390
398
|
shape: "a string",
|
|
391
399
|
describe:
|
|
392
|
-
"The sense text
|
|
393
|
-
"
|
|
400
|
+
"The sense text, overriding the word's or governance sources' senses. Unstated, " +
|
|
401
|
+
"a named dictionary word supplies all its senses and class; a named rank or office supplies its descriptions.",
|
|
394
402
|
}),
|
|
395
403
|
Object.freeze({
|
|
396
404
|
name: "pronunciation",
|
|
397
405
|
kind: "string",
|
|
398
406
|
shape: "a respelling such as `sol-MEH-reh`",
|
|
399
|
-
describe:
|
|
407
|
+
describe:
|
|
408
|
+
"A respelling, held to the same rule as a word's; the dictionary word's when unstated.",
|
|
400
409
|
}),
|
|
401
410
|
Object.freeze({
|
|
402
411
|
name: "language",
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
Structure in front matter:
|
|
8
8
|
related:
|
|
9
|
+
seeAlso: [{ title, url, type, gloss? }, ...] # deliberate references
|
|
9
10
|
backlinks: [{ title, url, type }, ...] # pages that link here
|
|
10
11
|
mentions: [{ title, url, type }, ...] # pages this page links to
|
|
11
12
|
|
|
12
13
|
Rendering strategy:
|
|
13
|
-
-
|
|
14
|
+
- Render deliberate references in authored order, with their glosses.
|
|
15
|
+
- Merge both inferred directions into a single deduped pool (direction
|
|
14
16
|
doesn't matter to a reader — a connection is a connection).
|
|
15
17
|
- Group by the referenced page's `type`.
|
|
16
18
|
- Emit one labelled group per type, its entries laid out as a
|
|
@@ -20,7 +22,7 @@
|
|
|
20
22
|
- The whole block is wrapped in a bordered, tinted card so it
|
|
21
23
|
visually sets off the end of the article.
|
|
22
24
|
|
|
23
|
-
Silent disappear: a page with no `related`, or whose
|
|
25
|
+
Silent disappear: a page with no `related`, or whose lists are all
|
|
24
26
|
empty, emits nothing at all rather than an empty heading. The rows are
|
|
25
27
|
computed first and the <section> is rendered only when there is something
|
|
26
28
|
to put in it.
|
|
@@ -34,15 +36,23 @@
|
|
|
34
36
|
{{- /* `default dict` keeps the lookups below safe on a page with no
|
|
35
37
|
`related` at all, so the whole computation stays flat. */ -}}
|
|
36
38
|
{{- $rel := .Params.related | default dict -}}
|
|
39
|
+
{{- $seeAlso := or $rel.seealso slice -}}
|
|
37
40
|
{{- $backlinks := or $rel.backlinks slice -}}
|
|
38
41
|
{{- $mentions := or $rel.mentions slice -}}
|
|
39
42
|
|
|
40
43
|
{{- /* Merge both directions, deduped by URL. `union` can't hash
|
|
41
44
|
slices of maps, so we walk the two lists in order and dedupe
|
|
42
|
-
by URL manually.
|
|
43
|
-
|
|
45
|
+
by URL manually. Deliberate references take priority; mentions
|
|
46
|
+
precede backlinks among inferred connections. */ -}}
|
|
44
47
|
{{- $seen := slice -}}
|
|
45
48
|
{{- $merged := slice -}}
|
|
49
|
+
{{- $deliberate := slice -}}
|
|
50
|
+
{{- range $seeAlso -}}
|
|
51
|
+
{{- if not (in $seen .url) -}}
|
|
52
|
+
{{- $seen = $seen | append .url -}}
|
|
53
|
+
{{- $deliberate = $deliberate | append . -}}
|
|
54
|
+
{{- end -}}
|
|
55
|
+
{{- end -}}
|
|
46
56
|
{{- range $mentions -}}
|
|
47
57
|
{{- if not (in $seen .url) -}}
|
|
48
58
|
{{- $seen = $seen | append .url -}}
|
|
@@ -101,12 +111,19 @@
|
|
|
101
111
|
{{- $rows = sort $rows "label" "asc" -}}
|
|
102
112
|
{{- end -}}
|
|
103
113
|
|
|
104
|
-
{{-
|
|
105
|
-
column 0 leaves the emitted HTML byte-identical to what pages with
|
|
106
|
-
`related` rendered before the guard was added. */ -}}
|
|
107
|
-
{{- if $rows -}}
|
|
114
|
+
{{- if or $rows $deliberate -}}
|
|
108
115
|
<section class="related" aria-labelledby="related-heading" data-pagefind-ignore>
|
|
109
116
|
<h2 id="related-heading" class="related-heading">Related</h2>
|
|
117
|
+
{{- with $deliberate }}
|
|
118
|
+
<div class="related-group">
|
|
119
|
+
<h3 class="related-group-label">See also</h3>
|
|
120
|
+
<ul class="related-group-grid">
|
|
121
|
+
{{- range . }}
|
|
122
|
+
<li><a href="{{ .url }}">{{ .title }}</a>{{ with .gloss }} — {{ . }}{{ end }}</li>
|
|
123
|
+
{{- end }}
|
|
124
|
+
</ul>
|
|
125
|
+
</div>
|
|
126
|
+
{{- end }}
|
|
110
127
|
{{- range $rows }}
|
|
111
128
|
<div class="related-group">
|
|
112
129
|
<h3 class="related-group-label">{{ .label }}</h3>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.9.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",
|
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
"dependencies": {
|
|
133
133
|
"@duckdb/node-api": "^1.5.5-r.4",
|
|
134
134
|
"@foundryvtt/foundryvtt-cli": "^3.0.4",
|
|
135
|
+
"@puppeteer/browsers": "^3.2.5",
|
|
135
136
|
"@viz-js/viz": "^3.30.0",
|
|
136
137
|
"acorn": "^8.18.0",
|
|
137
138
|
"archiver": "^8.0.0",
|
package/types/container.d.mts
CHANGED
|
@@ -247,8 +247,8 @@ export function s3RunArgs(plan: S3Plan): string[];
|
|
|
247
247
|
*/
|
|
248
248
|
export function s3ShellArgs(plan: S3Plan, command: string): string[];
|
|
249
249
|
/**
|
|
250
|
-
* Start the S3 server on its network
|
|
251
|
-
*
|
|
250
|
+
* Start or reuse the S3 server on its network, wait for readiness, ensure the
|
|
251
|
+
* bucket exists, and copy the `awsConfig` into the data root.
|
|
252
252
|
*
|
|
253
253
|
* @param {object} opts
|
|
254
254
|
* @param {S3Plan} opts.plan - The S3 plan.
|
|
@@ -261,7 +261,8 @@ export function s3ShellArgs(plan: S3Plan, command: string): string[];
|
|
|
261
261
|
* @param {(ms: number) => void} [opts.sleep] - Waits.
|
|
262
262
|
* @param {number} [opts.attempts] - How often to ask the server whether it is
|
|
263
263
|
* ready.
|
|
264
|
-
* @throws {Error} When an input
|
|
264
|
+
* @throws {Error} When an input is not a readable regular file, a reused
|
|
265
|
+
* identities mount is incompatible, or the S3 HTTP service never answers.
|
|
265
266
|
*/
|
|
266
267
|
export function ensureS3({ plan, dataRoot, log, run, capture, sleep, attempts, }: {
|
|
267
268
|
plan: S3Plan;
|
package/types/e2e.d.mts
CHANGED
|
@@ -236,6 +236,32 @@ export function waitForWorld({ url, container, stage, dataRoot, readLog, timeout
|
|
|
236
236
|
* @returns {string[]} The executables to resolve, in the order to report them.
|
|
237
237
|
*/
|
|
238
238
|
export function suiteExecutables(command: readonly string[]): string[];
|
|
239
|
+
/**
|
|
240
|
+
* The browser cache survives reseeding and container teardown.
|
|
241
|
+
* @param {NodeJS.ProcessEnv} [env] - Environment overrides.
|
|
242
|
+
* @returns {string} The browser cache directory.
|
|
243
|
+
*/
|
|
244
|
+
export function browserCacheDir(env?: NodeJS.ProcessEnv): string;
|
|
245
|
+
/**
|
|
246
|
+
* Supply pinned Chrome only to a recognizable Cypress command without a browser.
|
|
247
|
+
* Explicit browser arguments remain verbatim, including Electron and paths.
|
|
248
|
+
* @param {object} opts
|
|
249
|
+
* @param {readonly string[]} opts.command - The configured suite command.
|
|
250
|
+
* @param {string[]} [opts.args] - Extra suite arguments.
|
|
251
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Cache overrides.
|
|
252
|
+
* @param {string} [opts.platform] - Host operating system.
|
|
253
|
+
* @param {string} [opts.arch] - Host architecture.
|
|
254
|
+
* @param {(message: string) => void} [opts.log] - Progress reporting.
|
|
255
|
+
* @returns {Promise<string[]>} Arguments to append to the suite command.
|
|
256
|
+
*/
|
|
257
|
+
export function prepareSuiteBrowser({ command, args, env, platform, arch, log, }: {
|
|
258
|
+
command: readonly string[];
|
|
259
|
+
args?: string[] | undefined;
|
|
260
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
261
|
+
platform?: string | undefined;
|
|
262
|
+
arch?: string | undefined;
|
|
263
|
+
log?: ((message: string) => void) | undefined;
|
|
264
|
+
}): Promise<string[]>;
|
|
239
265
|
/**
|
|
240
266
|
* Find an executable the way the child process will: a path is a path, and a
|
|
241
267
|
* bare name is looked for in the repository's `node_modules/.bin` first, then
|
|
@@ -467,6 +493,8 @@ export const E2E_GM_ID: "heroiclandsE2EGM";
|
|
|
467
493
|
export const E2E_SCENE_ID: "heroiclandsScene";
|
|
468
494
|
/** How the suite may be run. */
|
|
469
495
|
export const E2E_MODES: readonly string[];
|
|
496
|
+
/** The Chrome for Testing build shared by local and CI Cypress runs. */
|
|
497
|
+
export const CHROME_BUILD_ID: "155.0.8059.84";
|
|
470
498
|
/**
|
|
471
499
|
* The disposable world's identity and credentials.
|
|
472
500
|
*/
|
|
@@ -631,8 +631,8 @@ export class BasePackCompiler {
|
|
|
631
631
|
compile(): Promise<void>;
|
|
632
632
|
currentNote: {
|
|
633
633
|
absPath: string;
|
|
634
|
-
bodyLine:
|
|
635
|
-
bodyColumn:
|
|
634
|
+
bodyLine: any;
|
|
635
|
+
bodyColumn: any;
|
|
636
636
|
} | undefined;
|
|
637
637
|
#private;
|
|
638
638
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* created when none is passed, and returned on the corpus either way — a note
|
|
16
16
|
* the index refuses is a note the compile must still *report*, exactly as the
|
|
17
17
|
* compile loop reported it when the loop was the first to see it.
|
|
18
|
-
* @returns {Promise<{records: object[], linkIndex: object, sqlTables: Map<string, object[]>|undefined, problems: object[]}>} The corpus,
|
|
18
|
+
* @returns {Promise<{records: object[], parsed: Map<string, object>, linkIndex: object, sqlTables: Map<string, object[]>|undefined, problems: object[]}>} The corpus,
|
|
19
19
|
* its indexes, and the notes it could not record.
|
|
20
20
|
*/
|
|
21
21
|
export function buildCompileCorpus({ contentBase, assetsBase, skipDirectories, router, config, problems, }: {
|
|
@@ -27,6 +27,7 @@ export function buildCompileCorpus({ contentBase, assetsBase, skipDirectories, r
|
|
|
27
27
|
problems?: object[] | undefined;
|
|
28
28
|
}): Promise<{
|
|
29
29
|
records: object[];
|
|
30
|
+
parsed: Map<string, object>;
|
|
30
31
|
linkIndex: object;
|
|
31
32
|
sqlTables: Map<string, object[]> | undefined;
|
|
32
33
|
problems: object[];
|
|
@@ -94,6 +94,7 @@ export function asciiAliases(aliases: unknown): Array<string>;
|
|
|
94
94
|
* @param {string} options.relPath - Its path below the content root, POSIX-separated.
|
|
95
95
|
* @param {string} [options.absPath] - The file, read only on the failing path to
|
|
96
96
|
* locate the offending key.
|
|
97
|
+
* @param {string} [options.raw] - Source text already read by the caller.
|
|
97
98
|
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
98
99
|
* @param {string} [options.body] - The note's markdown body, for its anchors
|
|
99
100
|
* and for the one question that decides whether the note publishes a page. A
|
|
@@ -118,10 +119,11 @@ export function asciiAliases(aliases: unknown): Array<string>;
|
|
|
118
119
|
* otherwise be overwritten without a word. `file` and, where the file was
|
|
119
120
|
* read, `position` ride on the error.
|
|
120
121
|
*/
|
|
121
|
-
export function buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest, addressContext, dateContext, resolveRole, }: {
|
|
122
|
+
export function buildIndexRecord({ frontmatter, relPath, absPath, raw, contentPackage, body, bodyLine, manifest, addressContext, dateContext, resolveRole, }: {
|
|
122
123
|
frontmatter: Record<string, any>;
|
|
123
124
|
relPath: string;
|
|
124
125
|
absPath?: string | undefined;
|
|
126
|
+
raw?: string | undefined;
|
|
125
127
|
contentPackage: string;
|
|
126
128
|
body?: string | undefined;
|
|
127
129
|
bodyLine?: number | undefined;
|
|
@@ -131,12 +133,13 @@ export function buildIndexRecord({ frontmatter, relPath, absPath, contentPackage
|
|
|
131
133
|
resolveRole?: ((address: string) => string | undefined) | undefined;
|
|
132
134
|
}): Record<string, any>;
|
|
133
135
|
/** Derive every index record belonging to one parsed note. */
|
|
134
|
-
export function indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext, dateContext, resolveRole, }: {
|
|
136
|
+
export function indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, raw, contentPackage, manifest, addressContext, dateContext, resolveRole, }: {
|
|
135
137
|
frontmatter: any;
|
|
136
138
|
body: any;
|
|
137
139
|
bodyLine: any;
|
|
138
140
|
relPath: any;
|
|
139
141
|
absPath: any;
|
|
142
|
+
raw: any;
|
|
140
143
|
contentPackage: any;
|
|
141
144
|
manifest: any;
|
|
142
145
|
addressContext: any;
|
|
@@ -188,16 +191,20 @@ export function indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absP
|
|
|
188
191
|
* immediate parent whose package is not this one.
|
|
189
192
|
* @param {object[]} [options.problems] - Receives each conflict as a finding.
|
|
190
193
|
* Omitted, every conflict is collected and one Error lists them all.
|
|
194
|
+
* @param {Map<string, {raw: string}>} [options.parsed] - Notes already read, keyed by file.
|
|
191
195
|
* @returns {Array<Record<string, any>>} The same records.
|
|
192
196
|
* @throws {Error} When `problems` is omitted and any note's sources disagree.
|
|
193
197
|
*/
|
|
194
|
-
export function resolveCultures(records: Array<Record<string, any>>, { contentPackage, contentBase, foreignRecord, problems }: {
|
|
198
|
+
export function resolveCultures(records: Array<Record<string, any>>, { contentPackage, contentBase, foreignRecord, problems, parsed }: {
|
|
195
199
|
contentPackage: string;
|
|
196
200
|
contentBase?: string | undefined;
|
|
197
201
|
foreignRecord?: ((key: string) => ({
|
|
198
202
|
culture?: unknown;
|
|
199
203
|
} | undefined)) | undefined;
|
|
200
204
|
problems?: object[] | undefined;
|
|
205
|
+
parsed?: Map<string, {
|
|
206
|
+
raw: string;
|
|
207
|
+
}> | undefined;
|
|
201
208
|
}): Array<Record<string, any>>;
|
|
202
209
|
/**
|
|
203
210
|
* Read a content tree into index records, in the order they will be written.
|
|
@@ -233,9 +240,9 @@ export function resolveCultures(records: Array<Record<string, any>>, { contentPa
|
|
|
233
240
|
* @param {(key: string) => ({culture?: unknown}|undefined)} [options.foreignRecord] -
|
|
234
241
|
* Another package's entry for a canonical address, read for a parent that
|
|
235
242
|
* package publishes; see {@link resolveCultures}.
|
|
236
|
-
* @param {Map<string, {body: string, frontmatter: object|null}>} [options.parsed] -
|
|
237
|
-
* Filled with each note's body and
|
|
238
|
-
*
|
|
243
|
+
* @param {Map<string, {body: string, frontmatter: object|null, raw: string, bodyLine?: number, bodyColumn?: number}>} [options.parsed] -
|
|
244
|
+
* Filled with each note's source text, body coordinates and frontmatter as
|
|
245
|
+
* authored, keyed by file, for reuse throughout the command.
|
|
239
246
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
240
247
|
* not depend on directory-read order.
|
|
241
248
|
*/
|
|
@@ -252,6 +259,9 @@ export function collectContentIndex(contentBase: string, { contentPackage, skipD
|
|
|
252
259
|
parsed?: Map<string, {
|
|
253
260
|
body: string;
|
|
254
261
|
frontmatter: object | null;
|
|
262
|
+
raw: string;
|
|
263
|
+
bodyLine?: number;
|
|
264
|
+
bodyColumn?: number;
|
|
255
265
|
}> | undefined;
|
|
256
266
|
}): Array<Record<string, any>>;
|
|
257
267
|
/**
|
|
@@ -289,7 +299,7 @@ export function serializeContentIndex(records: Array<Record<string, any>>): stri
|
|
|
289
299
|
* aborting the derivation. Omitted, the note throws — which is the contract
|
|
290
300
|
* the emitter needs, since an index missing a note asserts that it does not
|
|
291
301
|
* exist.
|
|
292
|
-
* @param {Map<string, {body: string, frontmatter: object|null}>} [opts.parsed] -
|
|
302
|
+
* @param {Map<string, {body: string, frontmatter: object|null, raw: string, bodyLine?: number, bodyColumn?: number}>} [opts.parsed] -
|
|
293
303
|
* Filled with each note's body and authored frontmatter, keyed by the note's
|
|
294
304
|
* file. Handed to {@link module:engine/sql-tables.prepareTreeSqlTables}, it
|
|
295
305
|
* spares that pass reading and parsing every note again.
|
|
@@ -304,8 +314,24 @@ export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectori
|
|
|
304
314
|
parsed?: Map<string, {
|
|
305
315
|
body: string;
|
|
306
316
|
frontmatter: object | null;
|
|
317
|
+
raw: string;
|
|
318
|
+
bodyLine?: number;
|
|
319
|
+
bodyColumn?: number;
|
|
307
320
|
}> | undefined;
|
|
308
321
|
}): object[];
|
|
322
|
+
/**
|
|
323
|
+
* Prepare the complete bodies a content-index publication describes.
|
|
324
|
+
* Prose declarations use each source's audience. Native SQL uses the complete
|
|
325
|
+
* corpus, with target maps limited to that source's declared prose slugs.
|
|
326
|
+
* No output files are written, and the returned inputs are read-only to the emitter.
|
|
327
|
+
*
|
|
328
|
+
* @param {{contentBase?: string, config?: object}} [options]
|
|
329
|
+
* @returns {Promise<PreparedContentIndex>}
|
|
330
|
+
*/
|
|
331
|
+
export function prepareContentIndex({ contentBase, config }?: {
|
|
332
|
+
contentBase?: string;
|
|
333
|
+
config?: object;
|
|
334
|
+
}): Promise<PreparedContentIndex>;
|
|
309
335
|
/**
|
|
310
336
|
* Emit this package's content index.
|
|
311
337
|
*
|
|
@@ -315,6 +341,7 @@ export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectori
|
|
|
315
341
|
* @param {string} [options.outDir] - Where to write; defaults to the configured
|
|
316
342
|
* `paths.contentIndex`.
|
|
317
343
|
* @param {object} [options.config] - A resolved configuration; loaded when omitted.
|
|
344
|
+
* @param {PreparedContentIndex} [options.publication] - Complete preparation for this tree and configuration.
|
|
318
345
|
* @returns {{file: string, notes: number, assets: number, records: number,
|
|
319
346
|
* bytes: number, full: number, draft: number, stub: number,
|
|
320
347
|
* dictionary: {file: string, rows: number}, terms: {file: string, rows: number}}}
|
|
@@ -328,10 +355,11 @@ export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectori
|
|
|
328
355
|
* reader would take it as the authoritative statement that this package has
|
|
329
356
|
* no content.
|
|
330
357
|
*/
|
|
331
|
-
export function emitContentIndex({ contentBase, outDir, config }?: {
|
|
358
|
+
export function emitContentIndex({ contentBase, outDir, config, publication }?: {
|
|
332
359
|
contentBase?: string | undefined;
|
|
333
360
|
outDir?: string | undefined;
|
|
334
361
|
config?: object | undefined;
|
|
362
|
+
publication?: PreparedContentIndex | undefined;
|
|
335
363
|
}): {
|
|
336
364
|
file: string;
|
|
337
365
|
notes: number;
|
|
@@ -350,6 +378,55 @@ export function emitContentIndex({ contentBase, outDir, config }?: {
|
|
|
350
378
|
rows: number;
|
|
351
379
|
};
|
|
352
380
|
};
|
|
381
|
+
/**
|
|
382
|
+
* A captured source context, consumed without mutation by the synchronous emitter.
|
|
383
|
+
*/
|
|
384
|
+
export type PreparedContentIndex = {
|
|
385
|
+
/**
|
|
386
|
+
* - The exact resolved configuration for preparation.
|
|
387
|
+
*/
|
|
388
|
+
config: object;
|
|
389
|
+
/**
|
|
390
|
+
* - The absolute captured source root.
|
|
391
|
+
*/
|
|
392
|
+
contentBase: string;
|
|
393
|
+
/**
|
|
394
|
+
* - Complete index records, including hidden providers.
|
|
395
|
+
*/
|
|
396
|
+
records: object[];
|
|
397
|
+
/**
|
|
398
|
+
* - Authored sources by absolute file.
|
|
399
|
+
*/
|
|
400
|
+
parsed: Map<string, {
|
|
401
|
+
frontmatter: object | null;
|
|
402
|
+
body: string;
|
|
403
|
+
raw: string;
|
|
404
|
+
bodyLine?: number;
|
|
405
|
+
bodyColumn?: number;
|
|
406
|
+
}>;
|
|
407
|
+
/**
|
|
408
|
+
* - Complete rendered bodies in each note's audience.
|
|
409
|
+
*/
|
|
410
|
+
expandedBodies: Map<string, {
|
|
411
|
+
markdown: string;
|
|
412
|
+
lineMap: Array<{
|
|
413
|
+
line: number;
|
|
414
|
+
generated: boolean;
|
|
415
|
+
}>;
|
|
416
|
+
generatedFrom?: number;
|
|
417
|
+
}>;
|
|
418
|
+
/**
|
|
419
|
+
* - Complete native bodies with explicit SQL using the ALL corpus.
|
|
420
|
+
*/
|
|
421
|
+
foundryBodies: Map<string, {
|
|
422
|
+
markdown: string;
|
|
423
|
+
lineMap: Array<{
|
|
424
|
+
line: number;
|
|
425
|
+
generated: boolean;
|
|
426
|
+
}>;
|
|
427
|
+
generatedFrom?: number;
|
|
428
|
+
}>;
|
|
429
|
+
};
|
|
353
430
|
import { collectAnchors } from "./anchors.mjs";
|
|
354
431
|
import { authoredFrontmatter } from "./index-records.mjs";
|
|
355
432
|
import { DERIVED_KEYS } from "./index-records.mjs";
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* Prepared results.
|
|
13
13
|
* A note's generated sections — see {@link module:engine/generated-sections} —
|
|
14
14
|
* are appended to the body first, from the `generated` text the preparation
|
|
15
|
-
* set on its results, and expanded with it.
|
|
15
|
+
* set on its results, and expanded with it. Automatic lexical lists, carried
|
|
16
|
+
* on `lexical`, then fill their authored sections or append a new section.
|
|
16
17
|
*
|
|
17
18
|
* @returns {{markdown: string, errors: object[], warnings: object[], lineMap: object[],
|
|
18
19
|
* generatedFrom?: number}} `generatedFrom` is the 0-based line of the
|
|
@@ -32,12 +32,15 @@ export function glossaryCorpus(index: object): GlossaryCorpus;
|
|
|
32
32
|
*
|
|
33
33
|
* @param {object} index - The link index.
|
|
34
34
|
* @param {object} note - The culture or affiliation note.
|
|
35
|
+
* @param {object} [entry] - A glossary entry, whose resolved language selects
|
|
36
|
+
* its dictionary words. Absent, the note's own language is used.
|
|
35
37
|
* @returns {Map<string, string[]>} The terms; empty for a note no source reaches.
|
|
36
38
|
*/
|
|
37
|
-
export function noteTerms(index: object, note: object): Map<string, string[]>;
|
|
39
|
+
export function noteTerms(index: object, note: object, entry?: object): Map<string, string[]>;
|
|
38
40
|
/**
|
|
39
41
|
* Warn of an affiliation that reaches no culture, its own or one it inherits,
|
|
40
|
-
* in a tree that describes peoples.
|
|
42
|
+
* in a tree that describes peoples. Its ranks and offices then have no
|
|
43
|
+
* culture associations for term queries.
|
|
41
44
|
*
|
|
42
45
|
* @param {object} note - An `affiliation` note.
|
|
43
46
|
* @param {{index?: object}} [context]
|
|
@@ -63,4 +66,21 @@ export type GlossaryCorpus = {
|
|
|
63
66
|
* give it.
|
|
64
67
|
*/
|
|
65
68
|
terms: Map<string, Map<string, string[]>>;
|
|
69
|
+
/**
|
|
70
|
+
* - The ranks and
|
|
71
|
+
* offices each owner can name.
|
|
72
|
+
*/
|
|
73
|
+
governance: Map<string, Map<string, string[]>>;
|
|
74
|
+
/**
|
|
75
|
+
* - Each
|
|
76
|
+
* language's dictionary words.
|
|
77
|
+
*/
|
|
78
|
+
words: Map<string, Array<{
|
|
79
|
+
form: string;
|
|
80
|
+
gloss: unknown;
|
|
81
|
+
}>>;
|
|
82
|
+
/**
|
|
83
|
+
* - Each language's parent.
|
|
84
|
+
*/
|
|
85
|
+
families: Map<string, string | undefined>;
|
|
66
86
|
};
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* every surface shows it exactly as written, never recased.
|
|
14
14
|
* @property {string} shape - The shape in words, for a finding and the reference.
|
|
15
15
|
* @property {boolean} [required] - The key must be stated, with a value.
|
|
16
|
+
* @property {boolean} [allowEmpty] - A required string may be empty where
|
|
17
|
+
* empty text is a stated value.
|
|
16
18
|
* @property {boolean} [nullable] - An explicit `null` states the key, for a
|
|
17
19
|
* required key whose null has a meaning of its own.
|
|
18
20
|
* @property {string} [label] - The infobox row label, where humanising the key
|
|
@@ -117,6 +119,11 @@ export type InnerKeySpec = {
|
|
|
117
119
|
* - The key must be stated, with a value.
|
|
118
120
|
*/
|
|
119
121
|
required?: boolean | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* - A required string may be empty where
|
|
124
|
+
* empty text is a stated value.
|
|
125
|
+
*/
|
|
126
|
+
allowEmpty?: boolean | undefined;
|
|
120
127
|
/**
|
|
121
128
|
* - An explicit `null` states the key, for a
|
|
122
129
|
* required key whose null has a meaning of its own.
|
|
@@ -150,12 +150,14 @@ export function positionInFrontmatter(raw: string, key: string, value?: string,
|
|
|
150
150
|
* {@link positionInFrontmatter}.
|
|
151
151
|
* @param {boolean} [options.topLevel=false] - Require the key at column 1, so
|
|
152
152
|
* an identically named nested key cannot answer for it.
|
|
153
|
+
* @param {string} [options.raw] - Source text already read by the caller.
|
|
153
154
|
* @returns {{line?: number, column?: number}|undefined} Spreadable position
|
|
154
155
|
* fields, dropped rather than guessed when the file cannot be read or the key
|
|
155
156
|
* cannot be found — as `formatDiagnostic` requires.
|
|
156
157
|
*/
|
|
157
|
-
export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string, { topLevel }?: {
|
|
158
|
+
export function locateFrontmatterKey(absPath: string | undefined, key: string, value?: string, { topLevel, raw }?: {
|
|
158
159
|
topLevel?: boolean | undefined;
|
|
160
|
+
raw?: string | undefined;
|
|
159
161
|
}): {
|
|
160
162
|
line?: number;
|
|
161
163
|
column?: number;
|
|
@@ -195,11 +197,10 @@ export function positionOfLiteral(text: string, needle: string, occurrence?: num
|
|
|
195
197
|
* first occurrence is routinely not the one the finding is about — which is the
|
|
196
198
|
* one thing the located form exists to prevent.
|
|
197
199
|
*
|
|
198
|
-
* A path resolves the exact node
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* representation of the same file to keep in step.
|
|
200
|
+
* A path resolves the exact node. The most recently located text retains its
|
|
201
|
+
* document and line counter, so several findings in one note share a parse.
|
|
202
|
+
* A different text replaces that entry, including an edited copy of the same
|
|
203
|
+
* file. The loader returns plain data without source ranges.
|
|
203
204
|
*
|
|
204
205
|
* Every failure — unparseable text, an `.mjs` configuration, a path that
|
|
205
206
|
* resolves to nothing — yields `{}`, so a caller spreads the result and the
|