@heroiclands/package-build 23.9.0 → 23.10.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 +26 -0
- package/README.md +2 -0
- package/bin/content-commands.mjs +197 -68
- package/docs/api.md +336 -68
- package/docs/authoring/links-and-markup.md +114 -47
- package/docs/commands.md +92 -3
- package/docs/content-format.md +6 -1
- package/docs/reference/format-details.md +129 -14
- package/docs/reference/note-types.md +4 -4
- package/engine/anchors.mjs +8 -6
- package/engine/base-compiler.mjs +6 -4
- package/engine/changelog-group.mjs +58 -34
- package/engine/changelog-lint.mjs +6 -2
- package/engine/compendiums.mjs +15 -1
- package/engine/compile-corpus.mjs +14 -1
- package/engine/content-charset.mjs +13 -1
- package/engine/content-html.mjs +18 -4
- package/engine/content-icons.mjs +15 -1
- package/engine/content-images.mjs +28 -9
- package/engine/content-index.mjs +102 -23
- package/engine/content-links.mjs +27 -12
- package/engine/content-session.mjs +247 -0
- package/engine/content-sources.mjs +110 -0
- package/engine/content-spans.mjs +87 -8
- package/engine/content-tables.mjs +1 -0
- package/engine/content-tasklists.mjs +20 -4
- package/engine/diagnostics.mjs +17 -6
- package/engine/extension-attributes.mjs +28 -5
- package/engine/first-use.mjs +371 -0
- package/engine/foundry-entries.mjs +12 -9
- package/engine/generate.mjs +13 -4
- package/engine/helpers.mjs +43 -14
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +3 -0
- package/engine/journals.mjs +9 -5
- package/engine/language-data.mjs +2 -2
- package/engine/language-rows.mjs +4 -4
- package/engine/language-terms.mjs +4 -4
- package/engine/lexical-sections.mjs +5 -5
- package/engine/markup-validation.mjs +7 -2
- package/engine/metadata-index.mjs +1 -0
- package/engine/name-markup.mjs +114 -0
- package/engine/name-rows.mjs +70 -0
- package/engine/names.mjs +460 -0
- package/engine/note-addresses.mjs +25 -4
- package/engine/path-charset.mjs +24 -1
- package/engine/pdf-render.mjs +15 -1
- package/engine/site-index.mjs +5 -1
- package/engine/sql-tables.mjs +76 -15
- package/engine/stub-lint.mjs +4 -3
- package/engine/yaml-lint.mjs +3 -5
- package/hugo-theme/static/css/style.css +4 -0
- package/package.json +1 -1
- package/types/engine/anchors.d.mts +4 -2
- package/types/engine/compendiums.d.mts +5 -1
- package/types/engine/compile-corpus.d.mts +5 -2
- package/types/engine/content-charset.d.mts +5 -1
- package/types/engine/content-html.d.mts +5 -1
- package/types/engine/content-icons.d.mts +5 -1
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-index.d.mts +39 -19
- package/types/engine/content-links.d.mts +22 -8
- package/types/engine/content-session.d.mts +108 -0
- package/types/engine/content-sources.d.mts +32 -0
- package/types/engine/content-spans.d.mts +3 -5
- package/types/engine/content-tasklists.d.mts +5 -1
- package/types/engine/diagnostics.d.mts +10 -0
- package/types/engine/extension-attributes.d.mts +16 -2
- package/types/engine/first-use.d.mts +31 -0
- package/types/engine/foundry-entries.d.mts +11 -4
- package/types/engine/generate.d.mts +5 -1
- package/types/engine/helpers.d.mts +30 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/journals.d.mts +5 -2
- package/types/engine/lexical-sections.d.mts +3 -2
- package/types/engine/markup-validation.d.mts +2 -1
- package/types/engine/name-markup.d.mts +176 -0
- package/types/engine/name-rows.d.mts +35 -0
- package/types/engine/names.d.mts +356 -0
- package/types/engine/note-addresses.d.mts +3 -3
- package/types/engine/path-charset.d.mts +16 -1
- package/types/engine/site-build.d.mts +2 -2
- package/types/engine/site-index.d.mts +3 -1
- package/types/engine/sql-tables.d.mts +7 -2
- package/types/engine/stub-lint.d.mts +3 -1
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
|
|
86
86
|
import fs from "node:fs";
|
|
87
87
|
import path from "node:path";
|
|
88
|
+
import { capturedSources } from "./content-sources.mjs";
|
|
88
89
|
|
|
89
90
|
/**
|
|
90
91
|
* Tier 1 — the letters, and the two whitespace characters a file is made of.
|
|
@@ -407,16 +408,27 @@ export function checkText(text, file) {
|
|
|
407
408
|
* @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore
|
|
408
409
|
* in addition to the dot-directories always skipped.
|
|
409
410
|
* @param {readonly string[]} [opts.extensions] - File extensions to read.
|
|
411
|
+
* @param {Map<string, {raw: string}>} [opts.sources] - Exact captured source strings; omitted, walks and reads the tree.
|
|
410
412
|
* @returns {{findings: Array<{file: string, line: number, column: number,
|
|
411
413
|
* severity: "warning", message: string}>, files: number}} The findings, and how
|
|
412
414
|
* many files produced them.
|
|
413
415
|
*/
|
|
414
|
-
export function lintContentCharset(
|
|
416
|
+
export function lintContentCharset(
|
|
417
|
+
contentBase,
|
|
418
|
+
{ skipDirectories = [], extensions, sources } = {},
|
|
419
|
+
) {
|
|
415
420
|
const exts = new Set(extensions ?? [".md", ".markdown", ".yaml", ".yml", ".json"]);
|
|
416
421
|
const skip = new Set(skipDirectories);
|
|
417
422
|
/** @type {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} */
|
|
418
423
|
const findings = [];
|
|
419
424
|
let files = 0;
|
|
425
|
+
if (sources) {
|
|
426
|
+
for (const [full, text] of capturedSources(contentBase, sources, skipDirectories, exts)) {
|
|
427
|
+
files++;
|
|
428
|
+
findings.push(...checkText(text, path.relative(process.cwd(), full)));
|
|
429
|
+
}
|
|
430
|
+
return { findings, files };
|
|
431
|
+
}
|
|
420
432
|
|
|
421
433
|
/** @param {string} dir - Directory to descend into. */
|
|
422
434
|
const walk = (dir) => {
|
package/engine/content-html.mjs
CHANGED
|
@@ -52,9 +52,10 @@
|
|
|
52
52
|
|
|
53
53
|
import fs from "node:fs";
|
|
54
54
|
import path from "node:path";
|
|
55
|
+
import { capturedSources } from "./content-sources.mjs";
|
|
55
56
|
|
|
56
57
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
57
|
-
import { positionInBody } from "./diagnostics.mjs";
|
|
58
|
+
import { positionInBody, splitFrontmatter } from "./diagnostics.mjs";
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* A raw HTML tag, opening, closing or self-closing.
|
|
@@ -133,15 +134,28 @@ export function checkHtml(body, file, { bodyLine = 1, bodyColumn = 1 } = {}) {
|
|
|
133
134
|
* @param {object} [opts]
|
|
134
135
|
* @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore
|
|
135
136
|
* in addition to the dot-directories always skipped.
|
|
137
|
+
* @param {Map<string, {raw: string}>} [opts.sources] - Exact captured source strings; omitted, walks and reads the tree.
|
|
136
138
|
* @returns {{findings: Array<{file: string, line: number, column: number,
|
|
137
139
|
* severity: "warning", message: string}>, files: number}} The findings, and
|
|
138
140
|
* how many files were read.
|
|
139
141
|
*/
|
|
140
|
-
export function lintContentHtml(contentBase, { skipDirectories = [] } = {}) {
|
|
142
|
+
export function lintContentHtml(contentBase, { skipDirectories = [], sources } = {}) {
|
|
141
143
|
const skip = new Set(skipDirectories);
|
|
142
144
|
/** @type {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} */
|
|
143
145
|
const findings = [];
|
|
144
146
|
let files = 0;
|
|
147
|
+
if (sources) {
|
|
148
|
+
for (const [full, content] of capturedSources(
|
|
149
|
+
contentBase,
|
|
150
|
+
sources,
|
|
151
|
+
skipDirectories,
|
|
152
|
+
new Set([".md", ".markdown"]),
|
|
153
|
+
)) {
|
|
154
|
+
files++;
|
|
155
|
+
findings.push(...checkHtml(...bodyOf(content, path.relative(process.cwd(), full))));
|
|
156
|
+
}
|
|
157
|
+
return { findings, files };
|
|
158
|
+
}
|
|
145
159
|
|
|
146
160
|
/** @param {string} dir - Directory to descend into. */
|
|
147
161
|
const walk = (dir) => {
|
|
@@ -188,10 +202,10 @@ export function lintContentHtml(contentBase, { skipDirectories = [] } = {}) {
|
|
|
188
202
|
* arguments {@link checkHtml} takes.
|
|
189
203
|
*/
|
|
190
204
|
function bodyOf(content, file) {
|
|
191
|
-
const match = content
|
|
205
|
+
const match = splitFrontmatter(content);
|
|
192
206
|
if (!match) return [content, file, { bodyLine: 1, bodyColumn: 1 }];
|
|
193
207
|
|
|
194
|
-
const raw = match
|
|
208
|
+
const raw = match.body;
|
|
195
209
|
const body = raw.trim();
|
|
196
210
|
const bodyStart = content.length - raw.length + (raw.length - raw.trimStart().length);
|
|
197
211
|
const before = content.slice(0, bodyStart);
|
package/engine/content-icons.mjs
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import fs from "node:fs";
|
|
29
29
|
import path from "node:path";
|
|
30
|
+
import { capturedSources } from "./content-sources.mjs";
|
|
30
31
|
import { parseExtensionAttributes } from "./extension-attributes.mjs";
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -524,13 +525,26 @@ export function checkIconRegistry(registry, where = "icons") {
|
|
|
524
525
|
* @param {object} [opts]
|
|
525
526
|
* @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore.
|
|
526
527
|
* @param {IconRegistry} [opts.registry] - The package's registry.
|
|
528
|
+
* @param {Map<string, {raw: string}>} [opts.sources] - Exact captured source strings; omitted, walks and reads the tree.
|
|
527
529
|
* @returns {{findings: Array<{file: string, line: number, column: number,
|
|
528
530
|
* severity: "warning", message: string}>, files: number}} What it found.
|
|
529
531
|
*/
|
|
530
|
-
export function lintContentIcons(contentBase, { skipDirectories = [], registry } = {}) {
|
|
532
|
+
export function lintContentIcons(contentBase, { skipDirectories = [], registry, sources } = {}) {
|
|
531
533
|
const skip = new Set(skipDirectories);
|
|
532
534
|
const findings = [];
|
|
533
535
|
let files = 0;
|
|
536
|
+
if (sources) {
|
|
537
|
+
for (const [full, text] of capturedSources(
|
|
538
|
+
contentBase,
|
|
539
|
+
sources,
|
|
540
|
+
skipDirectories,
|
|
541
|
+
new Set([".md", ".markdown"]),
|
|
542
|
+
)) {
|
|
543
|
+
files++;
|
|
544
|
+
findings.push(...lintIcons(text, path.relative(process.cwd(), full), registry));
|
|
545
|
+
}
|
|
546
|
+
return { findings, files };
|
|
547
|
+
}
|
|
534
548
|
|
|
535
549
|
/** @param {string} dir - Directory to descend into. */
|
|
536
550
|
const walk = (dir) => {
|
|
@@ -80,10 +80,11 @@
|
|
|
80
80
|
|
|
81
81
|
import fs from "node:fs";
|
|
82
82
|
import path from "node:path";
|
|
83
|
+
import { capturedSources } from "./content-sources.mjs";
|
|
83
84
|
|
|
84
85
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
85
86
|
import { parseExtensionAttributes } from "./extension-attributes.mjs";
|
|
86
|
-
import { positionInBody } from "./diagnostics.mjs";
|
|
87
|
+
import { positionInBody, splitFrontmatter } from "./diagnostics.mjs";
|
|
87
88
|
import { FOOTNOTE_REFERENCE } from "./content-footnotes.mjs";
|
|
88
89
|
import { foundryAddressProblem, pathnameProblem, servesFoundry } from "./pathnames.mjs";
|
|
89
90
|
import { ASSET_ROLES } from "./asset-index.mjs";
|
|
@@ -431,16 +432,20 @@ export function standsAlone(text, start, end) {
|
|
|
431
432
|
const lineEnd = src.indexOf("\n", end);
|
|
432
433
|
const afterMatch = src.slice(end, lineEnd === -1 ? src.length : lineEnd);
|
|
433
434
|
const reference = TRAILING_FOOTNOTE_REFERENCE.exec(afterMatch);
|
|
434
|
-
if (!/^[ \t]
|
|
435
|
+
if (!/^[ \t]*\r?$/.test(reference ? afterMatch.slice(reference[0].length) : afterMatch))
|
|
435
436
|
return false;
|
|
436
437
|
|
|
437
438
|
const before = src.slice(0, lineStart);
|
|
438
|
-
const previous =
|
|
439
|
-
|
|
439
|
+
const previous =
|
|
440
|
+
before
|
|
441
|
+
.replace(/\r?\n$/, "")
|
|
442
|
+
.split(/\r?\n/)
|
|
443
|
+
.pop() ?? "";
|
|
444
|
+
if (before.trim() && !/\n[ \t]*\r?\n[ \t]*$/.test(before) && !DELIMITER.test(previous))
|
|
440
445
|
return false;
|
|
441
446
|
const after = lineEnd === -1 ? "" : src.slice(lineEnd);
|
|
442
|
-
const next = after.replace(/^\n/, "").split(
|
|
443
|
-
if (after.trim() && !/^\n[ \t]*\n/.test(after) && !DELIMITER.test(next)) return false;
|
|
447
|
+
const next = after.replace(/^\n/, "").split(/\r?\n/)[0] ?? "";
|
|
448
|
+
if (after.trim() && !/^\n[ \t]*\r?\n/.test(after) && !DELIMITER.test(next)) return false;
|
|
444
449
|
return true;
|
|
445
450
|
}
|
|
446
451
|
|
|
@@ -586,10 +591,10 @@ export function checkImages(
|
|
|
586
591
|
* arguments {@link checkImages} takes.
|
|
587
592
|
*/
|
|
588
593
|
function bodyOf(content, file) {
|
|
589
|
-
const match = content
|
|
594
|
+
const match = splitFrontmatter(content);
|
|
590
595
|
if (!match) return [content, file, { bodyLine: 1, bodyColumn: 1 }];
|
|
591
596
|
|
|
592
|
-
const raw = match
|
|
597
|
+
const raw = match.body;
|
|
593
598
|
const body = raw.trim();
|
|
594
599
|
const bodyStart = content.length - raw.length + (raw.length - raw.trimStart().length);
|
|
595
600
|
const before = content.slice(0, bodyStart);
|
|
@@ -619,15 +624,29 @@ function bodyOf(content, file) {
|
|
|
619
624
|
* in addition to the dot-directories always skipped.
|
|
620
625
|
* @param {object} [opts.config] - The resolved build configuration, passed to
|
|
621
626
|
* {@link checkImages} so an address is held to the Foundry surface too.
|
|
627
|
+
* @param {Map<string, {raw: string}>} [opts.sources] - Exact captured source strings; omitted, walks and reads the tree.
|
|
622
628
|
* @returns {{findings: Array<{file: string, line: number, column: number|undefined,
|
|
623
629
|
* severity: "error", message: string}>, files: number}} The findings, and how
|
|
624
630
|
* many files were read.
|
|
625
631
|
*/
|
|
626
|
-
export function lintContentImages(contentBase, { skipDirectories = [], config } = {}) {
|
|
632
|
+
export function lintContentImages(contentBase, { skipDirectories = [], config, sources } = {}) {
|
|
627
633
|
const skip = new Set(skipDirectories);
|
|
628
634
|
/** @type {Array<{file: string, line: number, column: number|undefined, severity: "error", message: string}>} */
|
|
629
635
|
const findings = [];
|
|
630
636
|
let files = 0;
|
|
637
|
+
if (sources) {
|
|
638
|
+
for (const [full, content] of capturedSources(
|
|
639
|
+
contentBase,
|
|
640
|
+
sources,
|
|
641
|
+
skipDirectories,
|
|
642
|
+
new Set([".md", ".markdown"]),
|
|
643
|
+
)) {
|
|
644
|
+
files++;
|
|
645
|
+
const [body, rel, at] = bodyOf(content, path.relative(process.cwd(), full));
|
|
646
|
+
findings.push(...checkImages(body, rel, { ...at, config }));
|
|
647
|
+
}
|
|
648
|
+
return { findings, files };
|
|
649
|
+
}
|
|
631
650
|
|
|
632
651
|
/** @param {string} dir - Directory to descend into. */
|
|
633
652
|
const walk = (dir) => {
|
package/engine/content-index.mjs
CHANGED
|
@@ -102,6 +102,9 @@ import {
|
|
|
102
102
|
cloneAddressState,
|
|
103
103
|
} from "./note-addresses.mjs";
|
|
104
104
|
import { AddressLink } from "./address-values.mjs";
|
|
105
|
+
import { scanSpans } from "./content-spans.mjs";
|
|
106
|
+
import { rebaseNameSpans } from "./name-markup.mjs";
|
|
107
|
+
import { analyzeNameSpans } from "./names.mjs";
|
|
105
108
|
import fs from "node:fs";
|
|
106
109
|
import path from "node:path";
|
|
107
110
|
|
|
@@ -469,6 +472,8 @@ function assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage, raw)
|
|
|
469
472
|
* and for the one question that decides whether the note publishes a page. A
|
|
470
473
|
* caller that states no body is stating an empty one, and the note is a stub.
|
|
471
474
|
* @param {number} [options.bodyLine] - The 1-based file line the body starts on.
|
|
475
|
+
* @param {number} [options.bodyColumn] - The 1-based UTF-16 column of its first character.
|
|
476
|
+
* @param {Iterable<string>} [options.nameTypes] - Additional bare name kinds from configured type declarations.
|
|
472
477
|
* @param {object} [options.manifest] - The package manifest, which the Foundry
|
|
473
478
|
* entries are derived against.
|
|
474
479
|
* @param {object} [options.addressContext] - Address resolution context.
|
|
@@ -477,6 +482,7 @@ function assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage, raw)
|
|
|
477
482
|
* a picture's address to the role its asset declares, so a figure anchor's
|
|
478
483
|
* `name` reads `Map 1` rather than `Figure 1` where it is due — see
|
|
479
484
|
* {@link module:engine/content-figures.scanFigures}.
|
|
485
|
+
* @param {import("./content-session.mjs").ContentSyntax} [options.syntax] - Session scanner projections.
|
|
480
486
|
* @returns {Record<string, any>} The record, keys sorted at every depth. A
|
|
481
487
|
* **stub** — a note with an empty body, on a type an empty body suppresses —
|
|
482
488
|
* carries `address` and `anchors` as `null`: it publishes no page, so it
|
|
@@ -496,13 +502,37 @@ export function buildIndexRecord({
|
|
|
496
502
|
contentPackage,
|
|
497
503
|
body,
|
|
498
504
|
bodyLine,
|
|
505
|
+
bodyColumn,
|
|
506
|
+
nameTypes,
|
|
499
507
|
manifest,
|
|
500
508
|
addressContext,
|
|
501
509
|
dateContext,
|
|
502
510
|
resolveRole,
|
|
511
|
+
syntax,
|
|
503
512
|
}) {
|
|
504
513
|
assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage, raw);
|
|
505
514
|
|
|
515
|
+
const source = body ?? "";
|
|
516
|
+
const spans = syntax ? syntax(source).spans.names : scanSpans(source).names;
|
|
517
|
+
const marked = analyzeNameSpans(
|
|
518
|
+
rebaseNameSpans(spans, { raw: raw ?? source, bodyLine, bodyColumn }),
|
|
519
|
+
{ file: absPath ?? relPath, types: nameTypes },
|
|
520
|
+
);
|
|
521
|
+
if (marked.findings.length) {
|
|
522
|
+
const findings = marked.findings.map(({ line, column, ...finding }) => ({
|
|
523
|
+
...finding,
|
|
524
|
+
position: {
|
|
525
|
+
...(line === undefined ? {} : { line }),
|
|
526
|
+
...(column === undefined ? {} : { column }),
|
|
527
|
+
},
|
|
528
|
+
}));
|
|
529
|
+
throw Object.assign(new Error(findings[0].message), {
|
|
530
|
+
file: findings[0].file,
|
|
531
|
+
position: findings[0].position,
|
|
532
|
+
findings,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
506
536
|
const posix = relPath.split(path.sep).join("/");
|
|
507
537
|
const folder = posix.includes("/") ? posix.slice(0, posix.lastIndexOf("/")) : "";
|
|
508
538
|
try {
|
|
@@ -545,6 +575,7 @@ export function buildIndexRecord({
|
|
|
545
575
|
...frontmatter,
|
|
546
576
|
...(Object.keys(resolvedDates).length ? { resolvedDates } : {}),
|
|
547
577
|
package: contentPackage,
|
|
578
|
+
names: marked.names,
|
|
548
579
|
address: stub ? null : address,
|
|
549
580
|
nameAscii: asciiName(frontmatter?.name?.full),
|
|
550
581
|
aliasesAscii: asciiAliases(frontmatter?.name?.aliases),
|
|
@@ -557,7 +588,7 @@ export function buildIndexRecord({
|
|
|
557
588
|
anchors:
|
|
558
589
|
stub ? null : (
|
|
559
590
|
[
|
|
560
|
-
...collectAnchors(body, bodyLine, resolveRole),
|
|
591
|
+
...collectAnchors(body, bodyLine, resolveRole, syntax),
|
|
561
592
|
...eventAnchors(frontmatter, raw ?? rawNote(absPath)),
|
|
562
593
|
]
|
|
563
594
|
.sort((a, b) => (a.line ?? 0) - (b.line ?? 0))
|
|
@@ -680,11 +711,16 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
|
|
|
680
711
|
);
|
|
681
712
|
}
|
|
682
713
|
|
|
683
|
-
/**
|
|
714
|
+
/**
|
|
715
|
+
* Derive every index record belonging to one parsed note.
|
|
716
|
+
* @param {Parameters<typeof buildIndexRecord>[0]} options - Authored note and its context.
|
|
717
|
+
*/
|
|
684
718
|
export function indexRecordsForNote({
|
|
685
719
|
frontmatter,
|
|
686
720
|
body,
|
|
687
721
|
bodyLine,
|
|
722
|
+
bodyColumn,
|
|
723
|
+
nameTypes,
|
|
688
724
|
relPath,
|
|
689
725
|
absPath,
|
|
690
726
|
raw,
|
|
@@ -693,12 +729,15 @@ export function indexRecordsForNote({
|
|
|
693
729
|
addressContext,
|
|
694
730
|
dateContext,
|
|
695
731
|
resolveRole,
|
|
732
|
+
syntax,
|
|
696
733
|
}) {
|
|
697
734
|
resolveNoteId(frontmatter, { pkg: contentPackage });
|
|
698
735
|
const record = buildIndexRecord({
|
|
699
736
|
frontmatter,
|
|
700
737
|
body,
|
|
701
738
|
bodyLine,
|
|
739
|
+
bodyColumn,
|
|
740
|
+
nameTypes,
|
|
702
741
|
relPath,
|
|
703
742
|
absPath,
|
|
704
743
|
raw,
|
|
@@ -707,6 +746,7 @@ export function indexRecordsForNote({
|
|
|
707
746
|
addressContext,
|
|
708
747
|
dateContext,
|
|
709
748
|
resolveRole,
|
|
749
|
+
syntax,
|
|
710
750
|
});
|
|
711
751
|
const records = [decodeIndexAddresses(record, addressContext ?? { package: contentPackage })];
|
|
712
752
|
const address = noteAddress(frontmatter, contentPackage);
|
|
@@ -1063,6 +1103,9 @@ export function resolveCultures(
|
|
|
1063
1103
|
* @param {Map<string, {body: string, frontmatter: object|null, raw: string, bodyLine?: number, bodyColumn?: number}>} [options.parsed] -
|
|
1064
1104
|
* Filled with each note's source text, body coordinates and frontmatter as
|
|
1065
1105
|
* authored, keyed by file, for reuse throughout the command.
|
|
1106
|
+
* @param {Map<string, {raw: string}>} [options.sources] - Selected captured source strings.
|
|
1107
|
+
* @param {import("./content-session.mjs").ContentSyntax} [options.syntax] - Session scanner projections.
|
|
1108
|
+
* @param {Iterable<string>} [options.nameTypes] - Additional bare name kinds from configured type declarations.
|
|
1066
1109
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
1067
1110
|
* not depend on directory-read order.
|
|
1068
1111
|
*/
|
|
@@ -1077,12 +1120,15 @@ export function collectContentIndex(
|
|
|
1077
1120
|
addressContext,
|
|
1078
1121
|
foreignRecord,
|
|
1079
1122
|
parsed = new Map(),
|
|
1123
|
+
sources,
|
|
1124
|
+
syntax,
|
|
1125
|
+
nameTypes,
|
|
1080
1126
|
},
|
|
1081
1127
|
) {
|
|
1082
1128
|
const records = [];
|
|
1083
1129
|
// Passed through rather than defaulted away: an absent scope is the
|
|
1084
1130
|
// caller's omission, and `walkMarkdownTree` says so.
|
|
1085
|
-
const walkOpts = { skipDirectories };
|
|
1131
|
+
const walkOpts = { skipDirectories, sources, ...(sources ? { renderDescription: false } : {}) };
|
|
1086
1132
|
|
|
1087
1133
|
// A package may ship assets and no notes at all, in which case there is no
|
|
1088
1134
|
// tree to walk and the index is its asset records. The caller decides
|
|
@@ -1171,10 +1217,13 @@ export function collectContentIndex(
|
|
|
1171
1217
|
contentPackage,
|
|
1172
1218
|
body,
|
|
1173
1219
|
bodyLine,
|
|
1220
|
+
bodyColumn,
|
|
1221
|
+
nameTypes,
|
|
1174
1222
|
manifest,
|
|
1175
1223
|
addressContext: decodeContext,
|
|
1176
1224
|
dateContext: dates,
|
|
1177
1225
|
resolveRole,
|
|
1226
|
+
syntax,
|
|
1178
1227
|
});
|
|
1179
1228
|
records.push(...made);
|
|
1180
1229
|
bodies.push({ record: made[0], body });
|
|
@@ -1260,11 +1309,12 @@ export function collectContentIndex(
|
|
|
1260
1309
|
const key = canonicalKey(contentPackage, "note", record.type, record.shortcode);
|
|
1261
1310
|
const sections = (isGmNote(record) ? allSections : publicSections).get(key);
|
|
1262
1311
|
if (!sections) continue;
|
|
1263
|
-
const expanded = insertLexicalSections(body, sections).markdown;
|
|
1312
|
+
const expanded = insertLexicalSections(body, sections, { syntax }).markdown;
|
|
1264
1313
|
projectPublicationBody(records, record, expanded, {
|
|
1265
1314
|
contentPackage,
|
|
1266
1315
|
manifest,
|
|
1267
1316
|
resolveRole,
|
|
1317
|
+
syntax,
|
|
1268
1318
|
});
|
|
1269
1319
|
}
|
|
1270
1320
|
|
|
@@ -1415,6 +1465,10 @@ function dependencyAddresses(config) {
|
|
|
1415
1465
|
* Filled with each note's body and authored frontmatter, keyed by the note's
|
|
1416
1466
|
* file. Handed to {@link module:engine/sql-tables.prepareTreeSqlTables}, it
|
|
1417
1467
|
* spares that pass reading and parsing every note again.
|
|
1468
|
+
* @param {Map<string, {raw: string}>} [opts.sources] - Selected source strings, in index walk order.
|
|
1469
|
+
* @param {import("./content-session.mjs").ContentSyntax} [opts.syntax] - Session scanner projections.
|
|
1470
|
+
* @param {Iterable<string>} [opts.nameTypes] - Bare name kinds, defaulting to configured item types.
|
|
1471
|
+
* @param {Map<string, object>} [opts.foreignIndex] - Captured dependency address view.
|
|
1418
1472
|
* @returns {object[]} One record per note, plus one per documentation entry.
|
|
1419
1473
|
*/
|
|
1420
1474
|
export function indexRecordsFor({
|
|
@@ -1424,6 +1478,10 @@ export function indexRecordsFor({
|
|
|
1424
1478
|
skipDirectories,
|
|
1425
1479
|
problems,
|
|
1426
1480
|
parsed,
|
|
1481
|
+
sources,
|
|
1482
|
+
syntax,
|
|
1483
|
+
nameTypes,
|
|
1484
|
+
foreignIndex,
|
|
1427
1485
|
} = {}) {
|
|
1428
1486
|
const resolved = config ?? loadPackConfig();
|
|
1429
1487
|
const tree = contentBase ?? resolved.paths.content;
|
|
@@ -1434,7 +1492,7 @@ export function indexRecordsFor({
|
|
|
1434
1492
|
// a package whose parents are all its own reads no dependency index.
|
|
1435
1493
|
let foreign;
|
|
1436
1494
|
const foreignRecord = (key) => {
|
|
1437
|
-
foreign ??= loadForeignIndexes(resolved, [resolved.contentPackage]).index;
|
|
1495
|
+
foreign ??= foreignIndex ?? loadForeignIndexes(resolved, [resolved.contentPackage]).index;
|
|
1438
1496
|
return foreign.get(key);
|
|
1439
1497
|
};
|
|
1440
1498
|
return collectContentIndex(tree, {
|
|
@@ -1446,10 +1504,13 @@ export function indexRecordsFor({
|
|
|
1446
1504
|
skipDirectories: skipDirectories ?? resolved.skipDirectories,
|
|
1447
1505
|
assetsBase: assetsBase ?? resolved.paths.assets,
|
|
1448
1506
|
// Only the identities a UUID is a function of — see emitContentIndex.
|
|
1449
|
-
manifest: foundryIdentities(resolved),
|
|
1507
|
+
manifest: { ...foundryIdentities(resolved), syntax },
|
|
1450
1508
|
problems,
|
|
1451
1509
|
foreignRecord,
|
|
1452
1510
|
parsed,
|
|
1511
|
+
sources,
|
|
1512
|
+
syntax,
|
|
1513
|
+
nameTypes: nameTypes ?? resolved.itemTypes,
|
|
1453
1514
|
});
|
|
1454
1515
|
}
|
|
1455
1516
|
|
|
@@ -1516,17 +1577,20 @@ function publicationExpressions(expanded, context) {
|
|
|
1516
1577
|
* corpus, with target maps limited to that source's declared prose slugs.
|
|
1517
1578
|
* No output files are written, and the returned inputs are read-only to the emitter.
|
|
1518
1579
|
*
|
|
1519
|
-
* @param {{contentBase?: string, config?: object}} [options]
|
|
1580
|
+
* @param {{contentBase?: string, config?: object, session?: import("./content-session.mjs").ContentSession}} [options]
|
|
1520
1581
|
* @returns {Promise<PreparedContentIndex>}
|
|
1521
1582
|
*/
|
|
1522
|
-
export async function prepareContentIndex({ contentBase, config } = {}) {
|
|
1583
|
+
export async function prepareContentIndex({ contentBase, config, session } = {}) {
|
|
1523
1584
|
const resolved = config ?? loadPackConfig();
|
|
1524
1585
|
const tree = path.resolve(contentBase ?? resolved.paths.content);
|
|
1525
|
-
|
|
1526
|
-
const
|
|
1586
|
+
session?.assertContext({ config: resolved, contentBase: tree });
|
|
1587
|
+
const parsed = session?.parsed ?? new Map();
|
|
1588
|
+
const records =
|
|
1589
|
+
session?.records ?? indexRecordsFor({ contentBase: tree, config: resolved, parsed });
|
|
1590
|
+
if (session?.problems.length) publicationProblems(session.problems);
|
|
1527
1591
|
const expandedBodies = new Map();
|
|
1528
1592
|
const foundryBodies = new Map();
|
|
1529
|
-
const foreign = loadForeignIndexes(resolved, [resolved.contentPackage]);
|
|
1593
|
+
const foreign = session?.foreign ?? loadForeignIndexes(resolved, [resolved.contentPackage]);
|
|
1530
1594
|
publicationProblems(
|
|
1531
1595
|
foreign.stale.map((entry) => ({
|
|
1532
1596
|
file: tree,
|
|
@@ -1545,13 +1609,16 @@ export async function prepareContentIndex({ contentBase, config } = {}) {
|
|
|
1545
1609
|
])) {
|
|
1546
1610
|
const visible = records.filter((record) => audience === "all" || !isGmNote(record));
|
|
1547
1611
|
const sources = audience === "all" ? notes : notes.filter((record) => !isGmNote(record));
|
|
1548
|
-
const prepared =
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1612
|
+
const prepared =
|
|
1613
|
+
session ?
|
|
1614
|
+
await session.sqlTables(audience)
|
|
1615
|
+
: await prepareTreeSqlTables(tree, {
|
|
1616
|
+
config: resolved,
|
|
1617
|
+
records,
|
|
1618
|
+
parsed,
|
|
1619
|
+
audience,
|
|
1620
|
+
foreignIndex: foreign.index,
|
|
1621
|
+
});
|
|
1555
1622
|
const bodies = new Map();
|
|
1556
1623
|
const figuresByFile = new Map();
|
|
1557
1624
|
const figuresByAddress = new Map();
|
|
@@ -1615,6 +1682,7 @@ export async function prepareContentIndex({ contentBase, config } = {}) {
|
|
|
1615
1682
|
package: resolved.contentPackage,
|
|
1616
1683
|
records: visible,
|
|
1617
1684
|
foreignIndex: foreign.index,
|
|
1685
|
+
syntax: session?.syntax,
|
|
1618
1686
|
});
|
|
1619
1687
|
const dates = index.dateContext;
|
|
1620
1688
|
const events = eventNoteIndex(visible, {
|
|
@@ -1651,7 +1719,15 @@ export async function prepareContentIndex({ contentBase, config } = {}) {
|
|
|
1651
1719
|
expandedBodies.set(file, expanded);
|
|
1652
1720
|
}
|
|
1653
1721
|
}
|
|
1654
|
-
return {
|
|
1722
|
+
return {
|
|
1723
|
+
config: resolved,
|
|
1724
|
+
contentBase: tree,
|
|
1725
|
+
records,
|
|
1726
|
+
parsed,
|
|
1727
|
+
expandedBodies,
|
|
1728
|
+
foundryBodies,
|
|
1729
|
+
syntax: session?.syntax,
|
|
1730
|
+
};
|
|
1655
1731
|
}
|
|
1656
1732
|
|
|
1657
1733
|
/**
|
|
@@ -1663,6 +1739,7 @@ export async function prepareContentIndex({ contentBase, config } = {}) {
|
|
|
1663
1739
|
* @property {Map<string, {frontmatter: object|null, body: string, raw: string, bodyLine?: number, bodyColumn?: number}>} parsed - Authored sources by absolute file.
|
|
1664
1740
|
* @property {Map<string, {markdown: string, lineMap: Array<{line: number, generated: boolean}>, generatedFrom?: number}>} expandedBodies - Complete rendered bodies in each note's audience.
|
|
1665
1741
|
* @property {Map<string, {markdown: string, lineMap: Array<{line: number, generated: boolean}>, generatedFrom?: number}>} foundryBodies - Complete native bodies with explicit SQL using the ALL corpus.
|
|
1742
|
+
* @property {import("./content-session.mjs").ContentSyntax} [syntax] - Session-local exact-text scanner projections.
|
|
1666
1743
|
*/
|
|
1667
1744
|
|
|
1668
1745
|
/** Update owned copies from an actual output body, retaining authored locations and events. */
|
|
@@ -1670,7 +1747,7 @@ function projectPublicationBody(
|
|
|
1670
1747
|
records,
|
|
1671
1748
|
record,
|
|
1672
1749
|
body,
|
|
1673
|
-
{ contentPackage, manifest, resolveRole, foundryBody = body },
|
|
1750
|
+
{ contentPackage, manifest, resolveRole, foundryBody = body, syntax },
|
|
1674
1751
|
) {
|
|
1675
1752
|
const fm = authoredFrontmatter(record);
|
|
1676
1753
|
const address = noteAddress(fm, contentPackage);
|
|
@@ -1685,7 +1762,7 @@ function projectPublicationBody(
|
|
|
1685
1762
|
record.anchors =
|
|
1686
1763
|
stub ? null : (
|
|
1687
1764
|
[
|
|
1688
|
-
...collectAnchors(body, 1, resolveRole).map((anchor) => {
|
|
1765
|
+
...collectAnchors(body, 1, resolveRole, syntax).map((anchor) => {
|
|
1689
1766
|
const { line: _, ...generated } = anchor;
|
|
1690
1767
|
return {
|
|
1691
1768
|
...(original.get(anchor.slug) ?? generated),
|
|
@@ -1839,12 +1916,14 @@ export function emitContentIndex({ contentBase, outDir, config, publication } =
|
|
|
1839
1916
|
);
|
|
1840
1917
|
const body =
|
|
1841
1918
|
publication?.expandedBodies.get(file)?.markdown ??
|
|
1842
|
-
insertLexicalSections(parsed.get(file).body, lexical)
|
|
1919
|
+
insertLexicalSections(parsed.get(file).body, lexical, { syntax: publication?.syntax })
|
|
1920
|
+
.markdown;
|
|
1843
1921
|
projectPublicationBody(records, record, body, {
|
|
1844
1922
|
contentPackage,
|
|
1845
|
-
manifest: foundryIdentities(resolved),
|
|
1923
|
+
manifest: { ...foundryIdentities(resolved), syntax: publication?.syntax },
|
|
1846
1924
|
resolveRole: (address) => embedRole(art, address),
|
|
1847
1925
|
foundryBody: publication?.foundryBodies.get(file).markdown,
|
|
1926
|
+
syntax: publication?.syntax,
|
|
1848
1927
|
});
|
|
1849
1928
|
}
|
|
1850
1929
|
const assetBindings = checkForeignAssetBindings(resolved);
|
package/engine/content-links.mjs
CHANGED
|
@@ -59,6 +59,7 @@ import { cultureRecordProblems, cultureInheritedFields } from "./culture-parents
|
|
|
59
59
|
import path from "node:path";
|
|
60
60
|
|
|
61
61
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
62
|
+
import { splitFrontmatter } from "./diagnostics.mjs";
|
|
62
63
|
import { expandContentTables } from "./content-tables.mjs";
|
|
63
64
|
import { collectAnchors } from "./anchors.mjs";
|
|
64
65
|
// The corpus, and everything derived from it, read from the one place that
|
|
@@ -129,13 +130,11 @@ export function anchorsOf(body) {
|
|
|
129
130
|
* not hypothetical: this module carried its own anchor reader until the anchor
|
|
130
131
|
* anchor half, and the two disagreed about which anchors existed.
|
|
131
132
|
*
|
|
132
|
-
* **
|
|
133
|
-
* carries no note
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* before, since the walk read the file and it then read it again for the raw
|
|
138
|
-
* text.
|
|
133
|
+
* **Raw sources can come from the caller's capture.** The published index
|
|
134
|
+
* carries no note body, while a link lives in the prose. A caller that already
|
|
135
|
+
* captured the sources supplies `parsed`, so this pass uses those exact raw
|
|
136
|
+
* strings. Without a captured string, it opens the file for its bytes.
|
|
137
|
+
* Frontmatter, addresses and anchors still come from the records.
|
|
139
138
|
*
|
|
140
139
|
* The index holds each compiled document under its complete Address. Readable
|
|
141
140
|
* content uses `note`, and system documents use their declared system.
|
|
@@ -153,14 +152,27 @@ export function anchorsOf(body) {
|
|
|
153
152
|
* @param {object[]} [opts.records] - Index records the caller already derived,
|
|
154
153
|
* so a command that also needs them — every one of them does, to answer its
|
|
155
154
|
* `sql` tables — enumerates the corpus once rather than twice.
|
|
155
|
+
* @param {Map<string, {raw: string}>} [opts.parsed] - Sources captured alongside
|
|
156
|
+
* `records` in this invocation, keyed by absolute note path. A raw string is
|
|
157
|
+
* used verbatim; a missing string falls back to reading the file. The caller
|
|
158
|
+
* owns the map, which this function only reads.
|
|
156
159
|
* @param {object[]} [opts.problems] - Collects the notes the index cannot
|
|
157
160
|
* record, as diagnostics, instead of letting one of them abort the check
|
|
158
161
|
* before it has reported anything else.
|
|
162
|
+
* @param {ReturnType<import("./metadata-index.mjs").loadForeignIndexes>} [opts.foreign] - Captured dependency address and reference views.
|
|
159
163
|
* @returns {object} The notes, the index, and the resolvers built over it.
|
|
160
164
|
*/
|
|
161
165
|
export function buildLinkIndex(
|
|
162
166
|
contentBase,
|
|
163
|
-
{
|
|
167
|
+
{
|
|
168
|
+
config,
|
|
169
|
+
skipDirectories,
|
|
170
|
+
sqlTables,
|
|
171
|
+
records,
|
|
172
|
+
parsed,
|
|
173
|
+
problems,
|
|
174
|
+
foreign: capturedForeign,
|
|
175
|
+
} = {},
|
|
164
176
|
) {
|
|
165
177
|
const notes = [];
|
|
166
178
|
const frontmatterLinks = [];
|
|
@@ -221,8 +233,10 @@ export function buildLinkIndex(
|
|
|
221
233
|
const absPath = noteFile(contentBase, record);
|
|
222
234
|
// The raw text is kept beside the parsed body: a consumer's own checks
|
|
223
235
|
// may need what frontmatter carried, which the body has dropped.
|
|
224
|
-
const
|
|
225
|
-
const
|
|
236
|
+
const capturedRaw = parsed?.get(absPath)?.raw;
|
|
237
|
+
const raw =
|
|
238
|
+
typeof capturedRaw === "string" ? capturedRaw : fs.readFileSync(absPath, "utf8");
|
|
239
|
+
const body = splitFrontmatter(raw)?.body ?? raw;
|
|
226
240
|
const type = record.type.toLowerCase();
|
|
227
241
|
const note = { file: absPath, rel, fm, body, raw, type };
|
|
228
242
|
const hierarchyProblems = cultureRecordProblems(record);
|
|
@@ -295,9 +309,10 @@ export function buildLinkIndex(
|
|
|
295
309
|
// is never checked at all.
|
|
296
310
|
const localPackages = new Set([pkg]);
|
|
297
311
|
const foreign =
|
|
298
|
-
|
|
312
|
+
capturedForeign ??
|
|
313
|
+
(config ?
|
|
299
314
|
loadForeignIndexes(config, localPackages)
|
|
300
|
-
: { index: new Map(), packages: new Set(), stale: [] };
|
|
315
|
+
: { index: new Map(), packages: new Set(), stale: [] });
|
|
301
316
|
for (const v of foreign.index.values()) if (v.type) types.add(v.type);
|
|
302
317
|
|
|
303
318
|
// A stub counts. It publishes no page, so nothing may *link* to one, but its
|