@heroiclands/package-build 23.8.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 +53 -0
- package/README.md +2 -0
- package/bin/content-commands.mjs +207 -70
- package/docs/api.md +646 -78
- package/docs/authoring/first-note.md +1 -1
- package/docs/authoring/links-and-markup.md +253 -102
- package/docs/commands.md +111 -13
- package/docs/content-format.md +7 -2
- package/docs/diagnostics.md +1 -1
- package/docs/guides/packs.md +2 -1
- package/docs/guides/reference-pages.md +86 -63
- package/docs/reference/format-details.md +398 -137
- package/docs/reference/note-types.md +188 -174
- package/engine/anchors.mjs +15 -26
- 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 +17 -3
- package/engine/content-charset.mjs +13 -1
- package/engine/content-format.yaml +1 -0
- 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 +496 -16
- package/engine/content-links.mjs +70 -27
- 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 +9 -4
- package/engine/content-tasklists.mjs +20 -4
- package/engine/culture-reach.mjs +61 -34
- package/engine/data-keys.mjs +3 -1
- package/engine/diagnostics.mjs +17 -6
- package/engine/dictionary-layout.mjs +151 -19
- package/engine/dictionary-rows.mjs +129 -79
- package/engine/extension-attributes.mjs +28 -5
- package/engine/first-use.mjs +371 -0
- package/engine/foundry-entries.mjs +29 -8
- package/engine/generate.mjs +13 -4
- package/engine/glossary-entries.mjs +11 -11
- package/engine/heading-attributes.mjs +21 -28
- package/engine/helpers.mjs +54 -19
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +3 -0
- package/engine/journals.mjs +13 -6
- package/engine/language-data.mjs +55 -2
- package/engine/language-rows.mjs +4 -4
- package/engine/language-terms.mjs +90 -4
- package/engine/language-words.mjs +2 -2
- package/engine/lexical-sections.mjs +247 -0
- 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/note-claims.mjs +2 -0
- package/engine/note-state.mjs +9 -3
- package/engine/note-vocabulary.mjs +7 -6
- package/engine/path-charset.mjs +24 -1
- package/engine/pdf-build.mjs +9 -9
- package/engine/pdf-render.mjs +15 -1
- package/engine/site-build.mjs +16 -2
- package/engine/site-index.mjs +5 -1
- package/engine/sql-tables.mjs +324 -44
- package/engine/stub-lint.mjs +10 -5
- package/engine/web-wikilinks.mjs +8 -6
- package/engine/word-rows.mjs +20 -12
- package/engine/word-terms.mjs +15 -6
- 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 +102 -18
- 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-tables.d.mts +2 -1
- package/types/engine/content-tasklists.d.mts +5 -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 +10 -0
- package/types/engine/dictionary-layout.d.mts +35 -4
- package/types/engine/dictionary-rows.d.mts +9 -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 +14 -5
- package/types/engine/generate.d.mts +5 -1
- package/types/engine/heading-attributes.d.mts +2 -2
- package/types/engine/helpers.d.mts +33 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/journals.d.mts +5 -2
- package/types/engine/language-terms.d.mts +7 -0
- package/types/engine/lexical-sections.d.mts +96 -0
- 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/note-state.d.mts +2 -1
- package/types/engine/path-charset.d.mts +16 -1
- package/types/engine/site-build.d.mts +6 -3
- package/types/engine/site-index.d.mts +3 -1
- package/types/engine/sql-tables.d.mts +39 -4
- package/types/engine/stub-lint.d.mts +3 -1
- package/types/engine/word-rows.d.mts +9 -7
package/engine/content-index.mjs
CHANGED
|
@@ -99,8 +99,12 @@ import {
|
|
|
99
99
|
noteAddressContext,
|
|
100
100
|
encodeAddresses,
|
|
101
101
|
publishAddresses,
|
|
102
|
+
cloneAddressState,
|
|
102
103
|
} from "./note-addresses.mjs";
|
|
103
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";
|
|
104
108
|
import fs from "node:fs";
|
|
105
109
|
import path from "node:path";
|
|
106
110
|
|
|
@@ -113,9 +117,24 @@ import { addressSlug, canonicalKey } from "./content-address.mjs";
|
|
|
113
117
|
import { isAddressTuple, ownDocumentSystem, readCanonicalKey, renderAddress } from "./address.mjs";
|
|
114
118
|
import { NOTE_SYSTEM } from "./systems.mjs";
|
|
115
119
|
import { dictionaryAndTermRows, serializeRows } from "./dictionary-rows.mjs";
|
|
120
|
+
import { insertLexicalSections, lexicalSectionsFor } from "./lexical-sections.mjs";
|
|
121
|
+
import { isGmNote } from "./note-vocabulary.mjs";
|
|
116
122
|
import { attachCitations } from "./word-citations.mjs";
|
|
117
123
|
import { assetAddressIndex } from "./art-fields.mjs";
|
|
118
124
|
import { embedRole } from "./content-embeds.mjs";
|
|
125
|
+
import { expandContentTables } from "./content-tables.mjs";
|
|
126
|
+
import {
|
|
127
|
+
codeRegions,
|
|
128
|
+
FENCE_LINE,
|
|
129
|
+
matchAllOutsideCode,
|
|
130
|
+
parseHeaderArgs,
|
|
131
|
+
replaceOutsideCode,
|
|
132
|
+
} from "./code-fences.mjs";
|
|
133
|
+
import { PAGE_LIST_LANGUAGE } from "./page-lists.mjs";
|
|
134
|
+
import { EXPRESSION, renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
135
|
+
import { scanFigures } from "./content-figures.mjs";
|
|
136
|
+
import { eventNoteIndex } from "./event-fields.mjs";
|
|
137
|
+
import { derivedSections } from "./derived-sections.mjs";
|
|
119
138
|
// One reader for a note's anchors, shared with the link checker and with the
|
|
120
139
|
// builds that emit a link. Re-exported because this is where callers
|
|
121
140
|
// have always addressed it.
|
|
@@ -133,7 +152,7 @@ import { NO_SYSTEM } from "./document-subtypes.mjs";
|
|
|
133
152
|
*/
|
|
134
153
|
|
|
135
154
|
export { collectAnchors };
|
|
136
|
-
import { entriesForNote, foundryIdentities } from "./foundry-entries.mjs";
|
|
155
|
+
import { entriesForNote, foundryIdentities, LEAD_ANCHOR } from "./foundry-entries.mjs";
|
|
137
156
|
import { walkMarkdownTree } from "./helpers.mjs";
|
|
138
157
|
// The one statement of what an empty body means, shared with the lint and the
|
|
139
158
|
// SQL view that derives the ladder from the absence this writes.
|
|
@@ -453,6 +472,8 @@ function assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage, raw)
|
|
|
453
472
|
* and for the one question that decides whether the note publishes a page. A
|
|
454
473
|
* caller that states no body is stating an empty one, and the note is a stub.
|
|
455
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.
|
|
456
477
|
* @param {object} [options.manifest] - The package manifest, which the Foundry
|
|
457
478
|
* entries are derived against.
|
|
458
479
|
* @param {object} [options.addressContext] - Address resolution context.
|
|
@@ -461,6 +482,7 @@ function assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage, raw)
|
|
|
461
482
|
* a picture's address to the role its asset declares, so a figure anchor's
|
|
462
483
|
* `name` reads `Map 1` rather than `Figure 1` where it is due — see
|
|
463
484
|
* {@link module:engine/content-figures.scanFigures}.
|
|
485
|
+
* @param {import("./content-session.mjs").ContentSyntax} [options.syntax] - Session scanner projections.
|
|
464
486
|
* @returns {Record<string, any>} The record, keys sorted at every depth. A
|
|
465
487
|
* **stub** — a note with an empty body, on a type an empty body suppresses —
|
|
466
488
|
* carries `address` and `anchors` as `null`: it publishes no page, so it
|
|
@@ -480,13 +502,37 @@ export function buildIndexRecord({
|
|
|
480
502
|
contentPackage,
|
|
481
503
|
body,
|
|
482
504
|
bodyLine,
|
|
505
|
+
bodyColumn,
|
|
506
|
+
nameTypes,
|
|
483
507
|
manifest,
|
|
484
508
|
addressContext,
|
|
485
509
|
dateContext,
|
|
486
510
|
resolveRole,
|
|
511
|
+
syntax,
|
|
487
512
|
}) {
|
|
488
513
|
assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage, raw);
|
|
489
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
|
+
|
|
490
536
|
const posix = relPath.split(path.sep).join("/");
|
|
491
537
|
const folder = posix.includes("/") ? posix.slice(0, posix.lastIndexOf("/")) : "";
|
|
492
538
|
try {
|
|
@@ -529,6 +575,7 @@ export function buildIndexRecord({
|
|
|
529
575
|
...frontmatter,
|
|
530
576
|
...(Object.keys(resolvedDates).length ? { resolvedDates } : {}),
|
|
531
577
|
package: contentPackage,
|
|
578
|
+
names: marked.names,
|
|
532
579
|
address: stub ? null : address,
|
|
533
580
|
nameAscii: asciiName(frontmatter?.name?.full),
|
|
534
581
|
aliasesAscii: asciiAliases(frontmatter?.name?.aliases),
|
|
@@ -541,7 +588,7 @@ export function buildIndexRecord({
|
|
|
541
588
|
anchors:
|
|
542
589
|
stub ? null : (
|
|
543
590
|
[
|
|
544
|
-
...collectAnchors(body, bodyLine, resolveRole),
|
|
591
|
+
...collectAnchors(body, bodyLine, resolveRole, syntax),
|
|
545
592
|
...eventAnchors(frontmatter, raw ?? rawNote(absPath)),
|
|
546
593
|
]
|
|
547
594
|
.sort((a, b) => (a.line ?? 0) - (b.line ?? 0))
|
|
@@ -643,22 +690,37 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
|
|
|
643
690
|
// entry's derivation.
|
|
644
691
|
id: made ? entry.id : null,
|
|
645
692
|
nameAscii: asciiName(frontmatter?.name?.full),
|
|
646
|
-
address: made ? { slug: address.slug, canonical: entry.key } : null,
|
|
693
|
+
address: made ? { slug: address.slug, canonical: readCanonicalKey(entry.key) } : null,
|
|
647
694
|
// The record this is the documentation *for*. `documentation` is
|
|
648
695
|
// the forward link on that record, so either end reaches the other.
|
|
649
|
-
documents: address.canonical,
|
|
650
|
-
anchors:
|
|
696
|
+
documents: readCanonicalKey(address.canonical),
|
|
697
|
+
anchors:
|
|
698
|
+
made ?
|
|
699
|
+
anchors.map((anchor) => ({
|
|
700
|
+
...anchor,
|
|
701
|
+
link: new AddressLink(
|
|
702
|
+
readCanonicalKey(entry.key),
|
|
703
|
+
anchor.slug,
|
|
704
|
+
anchor.kind,
|
|
705
|
+
),
|
|
706
|
+
}))
|
|
707
|
+
: null,
|
|
651
708
|
foundry: foundryBlock(entry, NOTE_SYSTEM),
|
|
652
709
|
file,
|
|
653
710
|
})
|
|
654
711
|
);
|
|
655
712
|
}
|
|
656
713
|
|
|
657
|
-
/**
|
|
714
|
+
/**
|
|
715
|
+
* Derive every index record belonging to one parsed note.
|
|
716
|
+
* @param {Parameters<typeof buildIndexRecord>[0]} options - Authored note and its context.
|
|
717
|
+
*/
|
|
658
718
|
export function indexRecordsForNote({
|
|
659
719
|
frontmatter,
|
|
660
720
|
body,
|
|
661
721
|
bodyLine,
|
|
722
|
+
bodyColumn,
|
|
723
|
+
nameTypes,
|
|
662
724
|
relPath,
|
|
663
725
|
absPath,
|
|
664
726
|
raw,
|
|
@@ -667,12 +729,15 @@ export function indexRecordsForNote({
|
|
|
667
729
|
addressContext,
|
|
668
730
|
dateContext,
|
|
669
731
|
resolveRole,
|
|
732
|
+
syntax,
|
|
670
733
|
}) {
|
|
671
734
|
resolveNoteId(frontmatter, { pkg: contentPackage });
|
|
672
735
|
const record = buildIndexRecord({
|
|
673
736
|
frontmatter,
|
|
674
737
|
body,
|
|
675
738
|
bodyLine,
|
|
739
|
+
bodyColumn,
|
|
740
|
+
nameTypes,
|
|
676
741
|
relPath,
|
|
677
742
|
absPath,
|
|
678
743
|
raw,
|
|
@@ -681,6 +746,7 @@ export function indexRecordsForNote({
|
|
|
681
746
|
addressContext,
|
|
682
747
|
dateContext,
|
|
683
748
|
resolveRole,
|
|
749
|
+
syntax,
|
|
684
750
|
});
|
|
685
751
|
const records = [decodeIndexAddresses(record, addressContext ?? { package: contentPackage })];
|
|
686
752
|
const address = noteAddress(frontmatter, contentPackage);
|
|
@@ -1037,6 +1103,9 @@ export function resolveCultures(
|
|
|
1037
1103
|
* @param {Map<string, {body: string, frontmatter: object|null, raw: string, bodyLine?: number, bodyColumn?: number}>} [options.parsed] -
|
|
1038
1104
|
* Filled with each note's source text, body coordinates and frontmatter as
|
|
1039
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.
|
|
1040
1109
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
1041
1110
|
* not depend on directory-read order.
|
|
1042
1111
|
*/
|
|
@@ -1051,12 +1120,15 @@ export function collectContentIndex(
|
|
|
1051
1120
|
addressContext,
|
|
1052
1121
|
foreignRecord,
|
|
1053
1122
|
parsed = new Map(),
|
|
1123
|
+
sources,
|
|
1124
|
+
syntax,
|
|
1125
|
+
nameTypes,
|
|
1054
1126
|
},
|
|
1055
1127
|
) {
|
|
1056
1128
|
const records = [];
|
|
1057
1129
|
// Passed through rather than defaulted away: an absent scope is the
|
|
1058
1130
|
// caller's omission, and `walkMarkdownTree` says so.
|
|
1059
|
-
const walkOpts = { skipDirectories };
|
|
1131
|
+
const walkOpts = { skipDirectories, sources, ...(sources ? { renderDescription: false } : {}) };
|
|
1060
1132
|
|
|
1061
1133
|
// A package may ship assets and no notes at all, in which case there is no
|
|
1062
1134
|
// tree to walk and the index is its asset records. The caller decides
|
|
@@ -1145,10 +1217,13 @@ export function collectContentIndex(
|
|
|
1145
1217
|
contentPackage,
|
|
1146
1218
|
body,
|
|
1147
1219
|
bodyLine,
|
|
1220
|
+
bodyColumn,
|
|
1221
|
+
nameTypes,
|
|
1148
1222
|
manifest,
|
|
1149
1223
|
addressContext: decodeContext,
|
|
1150
1224
|
dateContext: dates,
|
|
1151
1225
|
resolveRole,
|
|
1226
|
+
syntax,
|
|
1152
1227
|
});
|
|
1153
1228
|
records.push(...made);
|
|
1154
1229
|
bodies.push({ record: made[0], body });
|
|
@@ -1222,6 +1297,27 @@ export function collectContentIndex(
|
|
|
1222
1297
|
for (const [note, inherited] of inheritance.values)
|
|
1223
1298
|
if (Object.keys(inherited).length) note.fm.data = { ...note.fm.data, ...inherited };
|
|
1224
1299
|
|
|
1300
|
+
// Automatic lexical sections require only the recorded frontmatter. Their
|
|
1301
|
+
// published anchors and journal targets therefore use the same pure body
|
|
1302
|
+
// projection as the surfaces, without making the index emitter async.
|
|
1303
|
+
// Public notes resolve providers in their audience, including lineage
|
|
1304
|
+
// through hidden languages, just as their build does.
|
|
1305
|
+
const visible = records.filter((record) => !isGmNote(record));
|
|
1306
|
+
const publicSections = lexicalSectionsFor(visible, records);
|
|
1307
|
+
const allSections = lexicalSectionsFor(records);
|
|
1308
|
+
for (const { record, body } of bodies) {
|
|
1309
|
+
const key = canonicalKey(contentPackage, "note", record.type, record.shortcode);
|
|
1310
|
+
const sections = (isGmNote(record) ? allSections : publicSections).get(key);
|
|
1311
|
+
if (!sections) continue;
|
|
1312
|
+
const expanded = insertLexicalSections(body, sections, { syntax }).markdown;
|
|
1313
|
+
projectPublicationBody(records, record, expanded, {
|
|
1314
|
+
contentPackage,
|
|
1315
|
+
manifest,
|
|
1316
|
+
resolveRole,
|
|
1317
|
+
syntax,
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1225
1321
|
// Sorted at every depth like a note's record, and for the same reason: the
|
|
1226
1322
|
// declaration order of the `asset` fields is a fact about the emitter, not
|
|
1227
1323
|
// about the content, and the artifact is meant to be byte-identical across
|
|
@@ -1369,6 +1465,10 @@ function dependencyAddresses(config) {
|
|
|
1369
1465
|
* Filled with each note's body and authored frontmatter, keyed by the note's
|
|
1370
1466
|
* file. Handed to {@link module:engine/sql-tables.prepareTreeSqlTables}, it
|
|
1371
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.
|
|
1372
1472
|
* @returns {object[]} One record per note, plus one per documentation entry.
|
|
1373
1473
|
*/
|
|
1374
1474
|
export function indexRecordsFor({
|
|
@@ -1378,6 +1478,10 @@ export function indexRecordsFor({
|
|
|
1378
1478
|
skipDirectories,
|
|
1379
1479
|
problems,
|
|
1380
1480
|
parsed,
|
|
1481
|
+
sources,
|
|
1482
|
+
syntax,
|
|
1483
|
+
nameTypes,
|
|
1484
|
+
foreignIndex,
|
|
1381
1485
|
} = {}) {
|
|
1382
1486
|
const resolved = config ?? loadPackConfig();
|
|
1383
1487
|
const tree = contentBase ?? resolved.paths.content;
|
|
@@ -1388,7 +1492,7 @@ export function indexRecordsFor({
|
|
|
1388
1492
|
// a package whose parents are all its own reads no dependency index.
|
|
1389
1493
|
let foreign;
|
|
1390
1494
|
const foreignRecord = (key) => {
|
|
1391
|
-
foreign ??= loadForeignIndexes(resolved, [resolved.contentPackage]).index;
|
|
1495
|
+
foreign ??= foreignIndex ?? loadForeignIndexes(resolved, [resolved.contentPackage]).index;
|
|
1392
1496
|
return foreign.get(key);
|
|
1393
1497
|
};
|
|
1394
1498
|
return collectContentIndex(tree, {
|
|
@@ -1400,11 +1504,307 @@ export function indexRecordsFor({
|
|
|
1400
1504
|
skipDirectories: skipDirectories ?? resolved.skipDirectories,
|
|
1401
1505
|
assetsBase: assetsBase ?? resolved.paths.assets,
|
|
1402
1506
|
// Only the identities a UUID is a function of — see emitContentIndex.
|
|
1403
|
-
manifest: foundryIdentities(resolved),
|
|
1507
|
+
manifest: { ...foundryIdentities(resolved), syntax },
|
|
1404
1508
|
problems,
|
|
1405
1509
|
foreignRecord,
|
|
1406
1510
|
parsed,
|
|
1511
|
+
sources,
|
|
1512
|
+
syntax,
|
|
1513
|
+
nameTypes: nameTypes ?? resolved.itemTypes,
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/** Active body directives whose output cannot be inferred synchronously. */
|
|
1518
|
+
function dynamicPublicationBody(body) {
|
|
1519
|
+
const source = String(body ?? "");
|
|
1520
|
+
return (
|
|
1521
|
+
[...matchAllOutsideCode(source, EXPRESSION)].length > 0 ||
|
|
1522
|
+
codeRegions(source, { spans: false, poetryAsProse: false }).some(({ start, end }) => {
|
|
1523
|
+
const opener = FENCE_LINE.exec(source.slice(start, end).split("\n", 1)[0]);
|
|
1524
|
+
const language = opener && parseHeaderArgs(opener[3]).language;
|
|
1525
|
+
return language === "sql" || language === PAGE_LIST_LANGUAGE;
|
|
1526
|
+
})
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/** Publication errors are located and always precede output creation. */
|
|
1531
|
+
function publicationProblems(problems) {
|
|
1532
|
+
if (!problems.length) return;
|
|
1533
|
+
throw Object.assign(new Error(problems.map(formatDiagnostic).join("\n")), { located: true });
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/** Render the shared expression language and carry inserted scalar lines back to their source. */
|
|
1537
|
+
function publicationExpressions(expanded, context) {
|
|
1538
|
+
const source = expanded.markdown;
|
|
1539
|
+
const additions = [];
|
|
1540
|
+
const findings = [];
|
|
1541
|
+
const markdown = replaceOutsideCode(source, EXPRESSION, (expression, _inside, offset) => {
|
|
1542
|
+
const line = source.slice(0, offset).split("\n").length - 1;
|
|
1543
|
+
const location = expanded.lineMap[line] ?? { line, generated: false };
|
|
1544
|
+
const result = renderMarkdownExpressions(expression, {
|
|
1545
|
+
...context,
|
|
1546
|
+
bodyLine: (context.bodyLine ?? 1) + location.line,
|
|
1547
|
+
});
|
|
1548
|
+
const column = offset - source.lastIndexOf("\n", offset - 1) - 1;
|
|
1549
|
+
findings.push(
|
|
1550
|
+
...result.findings.map((finding) => ({ ...finding, column: finding.column + column })),
|
|
1551
|
+
);
|
|
1552
|
+
const count = result.markdown.split("\n").length - 1;
|
|
1553
|
+
if (count) additions.push({ line, count, location });
|
|
1554
|
+
return result.markdown;
|
|
1407
1555
|
});
|
|
1556
|
+
publicationProblems(findings);
|
|
1557
|
+
const lineMap = expanded.lineMap.map((location) => ({ ...location }));
|
|
1558
|
+
let generatedFrom = expanded.generatedFrom;
|
|
1559
|
+
for (const addition of additions.sort((a, b) => b.line - a.line)) {
|
|
1560
|
+
lineMap.splice(
|
|
1561
|
+
addition.line + 1,
|
|
1562
|
+
0,
|
|
1563
|
+
...Array.from({ length: addition.count }, () => ({
|
|
1564
|
+
...addition.location,
|
|
1565
|
+
generated: true,
|
|
1566
|
+
})),
|
|
1567
|
+
);
|
|
1568
|
+
if (generatedFrom !== undefined && addition.line < expanded.generatedFrom)
|
|
1569
|
+
generatedFrom += addition.count;
|
|
1570
|
+
}
|
|
1571
|
+
return { markdown, lineMap, ...(generatedFrom === undefined ? {} : { generatedFrom }) };
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* Prepare the complete bodies a content-index publication describes.
|
|
1576
|
+
* Prose declarations use each source's audience. Native SQL uses the complete
|
|
1577
|
+
* corpus, with target maps limited to that source's declared prose slugs.
|
|
1578
|
+
* No output files are written, and the returned inputs are read-only to the emitter.
|
|
1579
|
+
*
|
|
1580
|
+
* @param {{contentBase?: string, config?: object, session?: import("./content-session.mjs").ContentSession}} [options]
|
|
1581
|
+
* @returns {Promise<PreparedContentIndex>}
|
|
1582
|
+
*/
|
|
1583
|
+
export async function prepareContentIndex({ contentBase, config, session } = {}) {
|
|
1584
|
+
const resolved = config ?? loadPackConfig();
|
|
1585
|
+
const tree = path.resolve(contentBase ?? resolved.paths.content);
|
|
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);
|
|
1591
|
+
const expandedBodies = new Map();
|
|
1592
|
+
const foundryBodies = new Map();
|
|
1593
|
+
const foreign = session?.foreign ?? loadForeignIndexes(resolved, [resolved.contentPackage]);
|
|
1594
|
+
publicationProblems(
|
|
1595
|
+
foreign.stale.map((entry) => ({
|
|
1596
|
+
file: tree,
|
|
1597
|
+
severity: "error",
|
|
1598
|
+
message: `unusable ${entry.package} content index: ${entry.reason}`,
|
|
1599
|
+
})),
|
|
1600
|
+
);
|
|
1601
|
+
// Dynamic import keeps SQL preparation out of the synchronous reader's import cycle.
|
|
1602
|
+
const { prepareTreeSqlTables } = await import("./sql-tables.mjs");
|
|
1603
|
+
const { buildSiteIndex, figureIndexKeys, resolveCrossNoteFigures } =
|
|
1604
|
+
await import("./site-index.mjs");
|
|
1605
|
+
const notes = records.filter(isNoteRecord);
|
|
1606
|
+
for (const audience of new Set([
|
|
1607
|
+
...notes.map((record) => (isGmNote(record) ? "all" : "public")),
|
|
1608
|
+
"all",
|
|
1609
|
+
])) {
|
|
1610
|
+
const visible = records.filter((record) => audience === "all" || !isGmNote(record));
|
|
1611
|
+
const sources = audience === "all" ? notes : notes.filter((record) => !isGmNote(record));
|
|
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
|
+
});
|
|
1622
|
+
const bodies = new Map();
|
|
1623
|
+
const figuresByFile = new Map();
|
|
1624
|
+
const figuresByAddress = new Map();
|
|
1625
|
+
const art = assetAddressIndex(visible, { config: resolved, foreign });
|
|
1626
|
+
const resolveRole = (address) => embedRole(art, address);
|
|
1627
|
+
const pages = visible.filter(isNoteRecord).flatMap((record) => {
|
|
1628
|
+
const file = noteFile(tree, record);
|
|
1629
|
+
const authored = parsed.get(file);
|
|
1630
|
+
const expanded = expandContentTables(authored.body, {
|
|
1631
|
+
source: file,
|
|
1632
|
+
sqlTables: prepared?.get(file),
|
|
1633
|
+
pageLists: prepared?.pageLists?.get(file),
|
|
1634
|
+
});
|
|
1635
|
+
publicationProblems(
|
|
1636
|
+
expanded.errors.map((problem) => ({
|
|
1637
|
+
file,
|
|
1638
|
+
line: (authored.bodyLine ?? 1) + problem.line,
|
|
1639
|
+
column: problem.column,
|
|
1640
|
+
severity: "error",
|
|
1641
|
+
message: problem.reason,
|
|
1642
|
+
})),
|
|
1643
|
+
);
|
|
1644
|
+
bodies.set(file, expanded);
|
|
1645
|
+
const byId = new Map(
|
|
1646
|
+
scanFigures(expanded.markdown, { resolveRole })
|
|
1647
|
+
.figures.filter((figure) => figure.id)
|
|
1648
|
+
.map((figure) => [
|
|
1649
|
+
figure.id,
|
|
1650
|
+
{
|
|
1651
|
+
label: figure.label,
|
|
1652
|
+
caption: figure.caption,
|
|
1653
|
+
hasCaption: figure.hasCaption,
|
|
1654
|
+
},
|
|
1655
|
+
]),
|
|
1656
|
+
);
|
|
1657
|
+
figuresByFile.set(file, byId);
|
|
1658
|
+
const address = noteAddress(record, resolved.contentPackage);
|
|
1659
|
+
if (!address) return [];
|
|
1660
|
+
for (const key of figureIndexKeys(
|
|
1661
|
+
resolved.contentPackage,
|
|
1662
|
+
record.type,
|
|
1663
|
+
record.shortcode,
|
|
1664
|
+
))
|
|
1665
|
+
figuresByAddress.set(key, byId);
|
|
1666
|
+
const slug = address.slug;
|
|
1667
|
+
return [
|
|
1668
|
+
{
|
|
1669
|
+
kind: "content",
|
|
1670
|
+
file,
|
|
1671
|
+
fm: authoredFrontmatter(record),
|
|
1672
|
+
body: authored.body,
|
|
1673
|
+
pkg: resolved.contentPackage,
|
|
1674
|
+
slug,
|
|
1675
|
+
url: `${resolved.site?.base || `/${resolved.contentPackage}/`}${slug}/`,
|
|
1676
|
+
name: record.name?.full,
|
|
1677
|
+
relPath: record.file.path,
|
|
1678
|
+
},
|
|
1679
|
+
];
|
|
1680
|
+
});
|
|
1681
|
+
const index = buildSiteIndex(pages, {
|
|
1682
|
+
package: resolved.contentPackage,
|
|
1683
|
+
records: visible,
|
|
1684
|
+
foreignIndex: foreign.index,
|
|
1685
|
+
syntax: session?.syntax,
|
|
1686
|
+
});
|
|
1687
|
+
const dates = index.dateContext;
|
|
1688
|
+
const events = eventNoteIndex(visible, {
|
|
1689
|
+
contentPackage: resolved.contentPackage,
|
|
1690
|
+
foreignIndex: foreign.index,
|
|
1691
|
+
dates,
|
|
1692
|
+
});
|
|
1693
|
+
for (const record of sources) {
|
|
1694
|
+
const file = noteFile(tree, record);
|
|
1695
|
+
const authored = parsed.get(file);
|
|
1696
|
+
const figures = figuresByFile.get(file);
|
|
1697
|
+
const expanded = publicationExpressions(bodies.get(file), {
|
|
1698
|
+
fm: authoredFrontmatter(record),
|
|
1699
|
+
dates,
|
|
1700
|
+
events,
|
|
1701
|
+
file,
|
|
1702
|
+
bodyLine: authored.bodyLine,
|
|
1703
|
+
sqlResults: prepared?.inline?.get(file),
|
|
1704
|
+
figures: {
|
|
1705
|
+
get: (id) => figures?.get(id),
|
|
1706
|
+
note: (target) =>
|
|
1707
|
+
resolveCrossNoteFigures(target, {
|
|
1708
|
+
contentTypes: index.contentTypes,
|
|
1709
|
+
packages: index.packages,
|
|
1710
|
+
noIndexPackages: index.noIndexPackages,
|
|
1711
|
+
contentPackage: index.contentPackage,
|
|
1712
|
+
siteIndexMap: index.index,
|
|
1713
|
+
figuresByAddress,
|
|
1714
|
+
}),
|
|
1715
|
+
},
|
|
1716
|
+
});
|
|
1717
|
+
if (audience === "all") foundryBodies.set(file, expanded);
|
|
1718
|
+
if ((isGmNote(record) ? "all" : "public") === audience)
|
|
1719
|
+
expandedBodies.set(file, expanded);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return {
|
|
1723
|
+
config: resolved,
|
|
1724
|
+
contentBase: tree,
|
|
1725
|
+
records,
|
|
1726
|
+
parsed,
|
|
1727
|
+
expandedBodies,
|
|
1728
|
+
foundryBodies,
|
|
1729
|
+
syntax: session?.syntax,
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/**
|
|
1734
|
+
* A captured source context, consumed without mutation by the synchronous emitter.
|
|
1735
|
+
* @typedef {object} PreparedContentIndex
|
|
1736
|
+
* @property {object} config - The exact resolved configuration for preparation.
|
|
1737
|
+
* @property {string} contentBase - The absolute captured source root.
|
|
1738
|
+
* @property {object[]} records - Complete index records, including hidden providers.
|
|
1739
|
+
* @property {Map<string, {frontmatter: object|null, body: string, raw: string, bodyLine?: number, bodyColumn?: number}>} parsed - Authored sources by absolute file.
|
|
1740
|
+
* @property {Map<string, {markdown: string, lineMap: Array<{line: number, generated: boolean}>, generatedFrom?: number}>} expandedBodies - Complete rendered bodies in each note's audience.
|
|
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.
|
|
1743
|
+
*/
|
|
1744
|
+
|
|
1745
|
+
/** Update owned copies from an actual output body, retaining authored locations and events. */
|
|
1746
|
+
function projectPublicationBody(
|
|
1747
|
+
records,
|
|
1748
|
+
record,
|
|
1749
|
+
body,
|
|
1750
|
+
{ contentPackage, manifest, resolveRole, foundryBody = body, syntax },
|
|
1751
|
+
) {
|
|
1752
|
+
const fm = authoredFrontmatter(record);
|
|
1753
|
+
const address = noteAddress(fm, contentPackage);
|
|
1754
|
+
if (!address) return;
|
|
1755
|
+
const original = new Map(
|
|
1756
|
+
(record.anchors ?? [])
|
|
1757
|
+
.filter((anchor) => anchor.kind === "prose")
|
|
1758
|
+
.map((anchor) => [anchor.slug, anchor]),
|
|
1759
|
+
);
|
|
1760
|
+
const stub = isStubNote(fm, body);
|
|
1761
|
+
record.address = stub ? null : { ...address, canonical: readCanonicalKey(address.canonical) };
|
|
1762
|
+
record.anchors =
|
|
1763
|
+
stub ? null : (
|
|
1764
|
+
[
|
|
1765
|
+
...collectAnchors(body, 1, resolveRole, syntax).map((anchor) => {
|
|
1766
|
+
const { line: _, ...generated } = anchor;
|
|
1767
|
+
return {
|
|
1768
|
+
...(original.get(anchor.slug) ?? generated),
|
|
1769
|
+
link: new AddressLink(
|
|
1770
|
+
readCanonicalKey(address.canonical),
|
|
1771
|
+
anchor.slug,
|
|
1772
|
+
anchor.kind,
|
|
1773
|
+
),
|
|
1774
|
+
};
|
|
1775
|
+
}),
|
|
1776
|
+
...(record.anchors ?? []).filter((anchor) => anchor.kind === "event"),
|
|
1777
|
+
]
|
|
1778
|
+
);
|
|
1779
|
+
const entries = foundryEntries({ frontmatter: fm, address, body: foundryBody, manifest });
|
|
1780
|
+
const declared = new Set(
|
|
1781
|
+
(record.anchors ?? [])
|
|
1782
|
+
.filter((anchor) => anchor.kind === "prose")
|
|
1783
|
+
.map((anchor) => anchor.slug),
|
|
1784
|
+
);
|
|
1785
|
+
// Native SQL keeps its complete corpus; only a declared prose target is public.
|
|
1786
|
+
for (const entry of [entries?.own, entries?.doc])
|
|
1787
|
+
if (entry?.anchors)
|
|
1788
|
+
entry.anchors = Object.fromEntries(
|
|
1789
|
+
Object.entries(entry.anchors).filter(
|
|
1790
|
+
([slug]) => slug === LEAD_ANCHOR || declared.has(slug),
|
|
1791
|
+
),
|
|
1792
|
+
);
|
|
1793
|
+
record.foundry = foundryBlock(entries?.own, ownDocumentSystem(fm.type));
|
|
1794
|
+
record.documentation = entries?.doc?.key ? readCanonicalKey(entries.doc.key) : null;
|
|
1795
|
+
if (entries?.doc?.key) {
|
|
1796
|
+
const doc = buildDocRecord({
|
|
1797
|
+
frontmatter: fm,
|
|
1798
|
+
address,
|
|
1799
|
+
entry: entries.doc,
|
|
1800
|
+
file: record.file,
|
|
1801
|
+
contentPackage,
|
|
1802
|
+
anchors: record.anchors ?? [],
|
|
1803
|
+
});
|
|
1804
|
+
const existing = records.find((one) => one.documents && one.file.path === record.file.path);
|
|
1805
|
+
if (existing) Object.assign(existing, doc);
|
|
1806
|
+
else records.push(doc);
|
|
1807
|
+
}
|
|
1408
1808
|
}
|
|
1409
1809
|
|
|
1410
1810
|
/**
|
|
@@ -1416,6 +1816,7 @@ export function indexRecordsFor({
|
|
|
1416
1816
|
* @param {string} [options.outDir] - Where to write; defaults to the configured
|
|
1417
1817
|
* `paths.contentIndex`.
|
|
1418
1818
|
* @param {object} [options.config] - A resolved configuration; loaded when omitted.
|
|
1819
|
+
* @param {PreparedContentIndex} [options.publication] - Complete preparation for this tree and configuration.
|
|
1419
1820
|
* @returns {{file: string, notes: number, assets: number, records: number,
|
|
1420
1821
|
* bytes: number, full: number, draft: number, stub: number,
|
|
1421
1822
|
* dictionary: {file: string, rows: number}, terms: {file: string, rows: number}}}
|
|
@@ -1429,9 +1830,9 @@ export function indexRecordsFor({
|
|
|
1429
1830
|
* reader would take it as the authoritative statement that this package has
|
|
1430
1831
|
* no content.
|
|
1431
1832
|
*/
|
|
1432
|
-
export function emitContentIndex({ contentBase, outDir, config } = {}) {
|
|
1433
|
-
const resolved = config ?? loadPackConfig();
|
|
1434
|
-
const tree = contentBase ?? resolved.paths.content;
|
|
1833
|
+
export function emitContentIndex({ contentBase, outDir, config, publication } = {}) {
|
|
1834
|
+
const resolved = config ?? publication?.config ?? loadPackConfig();
|
|
1835
|
+
const tree = path.resolve(contentBase ?? publication?.contentBase ?? resolved.paths.content);
|
|
1435
1836
|
const dir = outDir ?? resolved.paths.contentIndex;
|
|
1436
1837
|
const contentPackage = resolved.contentPackage;
|
|
1437
1838
|
|
|
@@ -1448,7 +1849,83 @@ export function emitContentIndex({ contentBase, outDir, config } = {}) {
|
|
|
1448
1849
|
// publishes pages is no part of an address, and depending on it would make
|
|
1449
1850
|
// the index refuse to build for a configuration that is perfectly able to
|
|
1450
1851
|
// state one.
|
|
1451
|
-
const
|
|
1852
|
+
const parsed = publication?.parsed ?? new Map();
|
|
1853
|
+
if (
|
|
1854
|
+
publication &&
|
|
1855
|
+
(publication.config !== resolved ||
|
|
1856
|
+
publication.contentBase !== tree ||
|
|
1857
|
+
!Array.isArray(publication.records) ||
|
|
1858
|
+
!(publication.parsed instanceof Map) ||
|
|
1859
|
+
!(publication.expandedBodies instanceof Map) ||
|
|
1860
|
+
!(publication.foundryBodies instanceof Map))
|
|
1861
|
+
)
|
|
1862
|
+
throw new Error(
|
|
1863
|
+
"Use await prepareContentIndex({ config, contentBase }) for this publication's source tree and configuration",
|
|
1864
|
+
);
|
|
1865
|
+
const records = cloneAddressState(
|
|
1866
|
+
publication?.records ?? indexRecordsFor({ contentBase: tree, config: resolved, parsed }),
|
|
1867
|
+
);
|
|
1868
|
+
const notes = records.filter(isNoteRecord);
|
|
1869
|
+
const needsPreparation =
|
|
1870
|
+
!publication &&
|
|
1871
|
+
(notes.some(
|
|
1872
|
+
(record) =>
|
|
1873
|
+
dynamicPublicationBody(parsed.get(noteFile(tree, record))?.body) ||
|
|
1874
|
+
record.data?.events?.length,
|
|
1875
|
+
) ||
|
|
1876
|
+
derivedSections(notes, {
|
|
1877
|
+
fileOf: (record) => noteFile(tree, record),
|
|
1878
|
+
config: resolved,
|
|
1879
|
+
foreignIndex: loadForeignIndexes(resolved, [contentPackage]).index,
|
|
1880
|
+
}).size > 0);
|
|
1881
|
+
if (needsPreparation)
|
|
1882
|
+
throw new Error(
|
|
1883
|
+
"Dynamic content-index publication requires await prepareContentIndex({ config, contentBase }); pass its result as emitContentIndex({ config, contentBase, publication })",
|
|
1884
|
+
);
|
|
1885
|
+
if (
|
|
1886
|
+
publication &&
|
|
1887
|
+
notes.some((record) => {
|
|
1888
|
+
const file = noteFile(tree, record);
|
|
1889
|
+
const body = publication.expandedBodies.get(file);
|
|
1890
|
+
const native = publication.foundryBodies.get(file);
|
|
1891
|
+
return (
|
|
1892
|
+
!parsed.has(file) ||
|
|
1893
|
+
typeof body?.markdown !== "string" ||
|
|
1894
|
+
!Array.isArray(body.lineMap) ||
|
|
1895
|
+
typeof native?.markdown !== "string" ||
|
|
1896
|
+
!Array.isArray(native.lineMap)
|
|
1897
|
+
);
|
|
1898
|
+
})
|
|
1899
|
+
)
|
|
1900
|
+
throw new Error(
|
|
1901
|
+
"Incomplete content-index bodies: run await prepareContentIndex({ config, contentBase }) before publication",
|
|
1902
|
+
);
|
|
1903
|
+
const art = assetAddressIndex(records, { config: resolved });
|
|
1904
|
+
const publicLexical =
|
|
1905
|
+
publication ? undefined : (
|
|
1906
|
+
lexicalSectionsFor(
|
|
1907
|
+
records.filter((record) => !isGmNote(record)),
|
|
1908
|
+
records,
|
|
1909
|
+
)
|
|
1910
|
+
);
|
|
1911
|
+
const allLexical = publication ? undefined : lexicalSectionsFor(records);
|
|
1912
|
+
for (const record of notes) {
|
|
1913
|
+
const file = noteFile(tree, record);
|
|
1914
|
+
const lexical = (isGmNote(record) ? allLexical : publicLexical)?.get(
|
|
1915
|
+
canonicalKey(contentPackage, "note", record.type, record.shortcode),
|
|
1916
|
+
);
|
|
1917
|
+
const body =
|
|
1918
|
+
publication?.expandedBodies.get(file)?.markdown ??
|
|
1919
|
+
insertLexicalSections(parsed.get(file).body, lexical, { syntax: publication?.syntax })
|
|
1920
|
+
.markdown;
|
|
1921
|
+
projectPublicationBody(records, record, body, {
|
|
1922
|
+
contentPackage,
|
|
1923
|
+
manifest: { ...foundryIdentities(resolved), syntax: publication?.syntax },
|
|
1924
|
+
resolveRole: (address) => embedRole(art, address),
|
|
1925
|
+
foundryBody: publication?.foundryBodies.get(file).markdown,
|
|
1926
|
+
syntax: publication?.syntax,
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1452
1929
|
const assetBindings = checkForeignAssetBindings(resolved);
|
|
1453
1930
|
if (assetBindings.length)
|
|
1454
1931
|
throw Object.assign(new Error(assetBindings.map(formatDiagnostic).join("\n")), {
|
|
@@ -1463,16 +1940,19 @@ export function emitContentIndex({ contentBase, outDir, config } = {}) {
|
|
|
1463
1940
|
|
|
1464
1941
|
const text = serializeContentIndex(records);
|
|
1465
1942
|
const file = path.join(dir, metadataFileName(contentPackage));
|
|
1466
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1467
|
-
fs.writeFileSync(file, text);
|
|
1468
1943
|
|
|
1469
1944
|
// The `dictionary` and `terms` relations as datafiles beside the index, for
|
|
1470
1945
|
// `jq`, an editor or another package. Nothing in the build reads them back.
|
|
1471
1946
|
const derived = dictionaryAndTermRows(records.map((record) => encodeAddresses(record)));
|
|
1947
|
+
const serialized = Object.fromEntries(
|
|
1948
|
+
["dictionary", "terms"].map((relation) => [relation, serializeRows(derived[relation])]),
|
|
1949
|
+
);
|
|
1950
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1951
|
+
fs.writeFileSync(file, text);
|
|
1472
1952
|
const datafiles = {};
|
|
1473
1953
|
for (const relation of ["dictionary", "terms"]) {
|
|
1474
1954
|
const rowsFile = path.join(dir, `${contentPackage}-${relation}.jsonl`);
|
|
1475
|
-
fs.writeFileSync(rowsFile,
|
|
1955
|
+
fs.writeFileSync(rowsFile, serialized[relation]);
|
|
1476
1956
|
datafiles[relation] = { file: rowsFile, rows: derived[relation].length };
|
|
1477
1957
|
}
|
|
1478
1958
|
|