@heroiclands/package-build 17.1.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1773 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +394 -111
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +78 -81
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/labels.mjs +191 -0
- package/manifest.mjs +43 -2
- package/package.json +19 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +64 -80
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +16 -38
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
package/engine/generate.mjs
CHANGED
|
@@ -46,14 +46,35 @@ import { foreignItemCatalogDirs } from "./foreign-catalog.mjs";
|
|
|
46
46
|
import { Items } from "../sohl/items.mjs";
|
|
47
47
|
import { Journals } from "./journals.mjs";
|
|
48
48
|
import { Actors } from "../sohl/actors.mjs";
|
|
49
|
+
import { Hm3Items } from "../hm3/items.mjs";
|
|
50
|
+
import { Hm3Actors } from "../hm3/actors.mjs";
|
|
49
51
|
import { Macros } from "./macros.mjs";
|
|
50
52
|
import { Scenes } from "./scenes.mjs";
|
|
51
|
-
import {
|
|
53
|
+
import { Bundles } from "./bundles.mjs";
|
|
54
|
+
import {
|
|
55
|
+
statsForPack,
|
|
56
|
+
loadFolders,
|
|
57
|
+
buildFolderResolver,
|
|
58
|
+
writeFolderDocs,
|
|
59
|
+
parseMarkdownFile,
|
|
60
|
+
folderFilename,
|
|
61
|
+
} from "./helpers.mjs";
|
|
62
|
+
import {
|
|
63
|
+
buildFolderNoteIndex,
|
|
64
|
+
collectFolderNotes,
|
|
65
|
+
FOLDER_TYPE,
|
|
66
|
+
folderDocument,
|
|
67
|
+
} from "./folder-notes.mjs";
|
|
52
68
|
import { countContentNotes } from "./content-tree.mjs";
|
|
53
69
|
import { emitDiagnostic } from "./diagnostics.mjs";
|
|
70
|
+
// The corpus every pass runs over, derived once (#243).
|
|
71
|
+
import { buildCompileCorpus } from "./compile-corpus.mjs";
|
|
72
|
+
// The record accessors only — see `engine/index-records.mjs` (#243).
|
|
73
|
+
import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
54
74
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
55
75
|
import { routerFor } from "./pack-router.mjs";
|
|
56
76
|
import { unclaimedNoteFindings } from "./note-claims.mjs";
|
|
77
|
+
import { contentPackage } from "./content-package.mjs";
|
|
57
78
|
|
|
58
79
|
/**
|
|
59
80
|
* The compiler class for each Foundry document type a pack may hold.
|
|
@@ -63,6 +84,15 @@ import { unclaimedNoteFindings } from "./note-claims.mjs";
|
|
|
63
84
|
* compiler here. Unknown types fail the build rather than defaulting, so a pack
|
|
64
85
|
* declaring a type nothing can compile is loud at the first pass instead of
|
|
65
86
|
* shipping empty.
|
|
87
|
+
*
|
|
88
|
+
* **Two of the six are a system's, and the SoHL pair is not a default.** An
|
|
89
|
+
* Item or an Actor *is* a system's data — both passes declare
|
|
90
|
+
* `requiresSystemBlock` — so which compiler a pack gets is decided together
|
|
91
|
+
* with which system it declares; see {@link SYSTEM_COMPILERS}. The four
|
|
92
|
+
* system-neutral passes have one implementation because a JournalEntry, a
|
|
93
|
+
* Macro, a Scene and an Adventure are Foundry's documents rather than any
|
|
94
|
+
* system's — an `Adventure` does not even have a `system` field, which is why a
|
|
95
|
+
* bundle spanning two systems is two documents (#259).
|
|
66
96
|
*/
|
|
67
97
|
const COMPILERS = {
|
|
68
98
|
Item: Items,
|
|
@@ -70,8 +100,44 @@ const COMPILERS = {
|
|
|
70
100
|
Actor: Actors,
|
|
71
101
|
Macro: Macros,
|
|
72
102
|
Scene: Scenes,
|
|
103
|
+
Adventure: Bundles,
|
|
73
104
|
};
|
|
74
105
|
|
|
106
|
+
/**
|
|
107
|
+
* The system-specific compilers, by the system a pack declares (#139).
|
|
108
|
+
*
|
|
109
|
+
* A repository feeding two systems declares one Item pack and one Actor pack
|
|
110
|
+
* per system — `harn-ensemble` has `actors-hm3` and `actors-sohl` — and each
|
|
111
|
+
* pack's `system:` is what says whose data model its documents are shaped for.
|
|
112
|
+
* That is the same field the `_stats` stamp, the item-catalogue scope and the
|
|
113
|
+
* `itemBuilders` lookup already read, so nothing new is declared to make the
|
|
114
|
+
* compiler follow it.
|
|
115
|
+
*
|
|
116
|
+
* A system with no entry — or a pack that declares none — falls back to
|
|
117
|
+
* {@link COMPILERS}. That keeps every single-system configuration meaning
|
|
118
|
+
* exactly what it did: SoHL's passes were the only ones, so they stay the
|
|
119
|
+
* answer where nothing says otherwise.
|
|
120
|
+
*
|
|
121
|
+
* @type {Readonly<Record<string, Readonly<Record<string, Function>>>>}
|
|
122
|
+
*/
|
|
123
|
+
const SYSTEM_COMPILERS = Object.freeze({
|
|
124
|
+
sohl: Object.freeze({ Item: Items, Actor: Actors }),
|
|
125
|
+
hm3: Object.freeze({ Item: Hm3Items, Actor: Hm3Actors }),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The compiler class a pack of one document type and one system gets.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} docType - The Foundry document type the pack holds.
|
|
132
|
+
* @param {string|null} [system] - The system the pack declares, if any.
|
|
133
|
+
* @returns {Function|undefined} The compiler class, or `undefined` for a
|
|
134
|
+
* document type nothing here compiles — which {@link generatePack} reports
|
|
135
|
+
* rather than defaulting past.
|
|
136
|
+
*/
|
|
137
|
+
export function compilerFor(docType, system = null) {
|
|
138
|
+
return (system && SYSTEM_COMPILERS[system]?.[docType]) || COMPILERS[docType];
|
|
139
|
+
}
|
|
140
|
+
|
|
75
141
|
/**
|
|
76
142
|
* Root of the build-only JSON tree for one pack.
|
|
77
143
|
*
|
|
@@ -122,6 +188,45 @@ export function itemPackJsonDirs(config = loadPackConfig(), system = null) {
|
|
|
122
188
|
.map((pack) => packJsonDir(pack.name, config));
|
|
123
189
|
}
|
|
124
190
|
|
|
191
|
+
/**
|
|
192
|
+
* The compiled JSON a bundle may hold copies of, by document type.
|
|
193
|
+
*
|
|
194
|
+
* An `Adventure` carries **copies**, not references, so a bundle resolves its
|
|
195
|
+
* `contents` against compiled output rather than against the content tree — the
|
|
196
|
+
* same arrangement the actors pass has for `itemsSourceDirs`, generalised to
|
|
197
|
+
* every document class an Adventure can hold (#259).
|
|
198
|
+
*
|
|
199
|
+
* Two kinds of pack are left out, each because it holds nothing a note
|
|
200
|
+
* addresses. A **prebuilt** pack's JSON is checked in rather than compiled, so
|
|
201
|
+
* no note is routed into it and nothing in it answers to an address. An
|
|
202
|
+
* **Adventure** pack holds Adventures, and Foundry's `contentFields` has no
|
|
203
|
+
* field for one — a bundle of bundles is not a shape the document admits.
|
|
204
|
+
*
|
|
205
|
+
* **Scoped to one system when the pack has one**, exactly as
|
|
206
|
+
* {@link itemPackJsonDirs} is: a pack declaring `system: sohl` reads that
|
|
207
|
+
* system's packs and the system-neutral ones, so a `(type, shortcode)` that
|
|
208
|
+
* exists in two systems is read out of the right catalogue. Asking for no
|
|
209
|
+
* system reads them all, which is every single-system build.
|
|
210
|
+
*
|
|
211
|
+
* @param {object} [config] - The resolved build configuration. Defaults to this
|
|
212
|
+
* repository's.
|
|
213
|
+
* @param {string|null} [system] - The system whose documents are wanted.
|
|
214
|
+
* Omitted or `null`, every pack is read.
|
|
215
|
+
* @returns {Record<string, string[]>} Each readable pack's JSON directory, by
|
|
216
|
+
* the Foundry document type it holds.
|
|
217
|
+
*/
|
|
218
|
+
export function bundleSourceJsonDirs(config = loadPackConfig(), system = null) {
|
|
219
|
+
/** @type {Record<string, string[]>} */
|
|
220
|
+
const dirs = {};
|
|
221
|
+
for (const pack of config.packs) {
|
|
222
|
+
if (pack.prebuilt) continue;
|
|
223
|
+
if (pack.type === "Adventure") continue;
|
|
224
|
+
if (system != null && pack.system && pack.system !== system) continue;
|
|
225
|
+
(dirs[pack.type] ??= []).push(packJsonDir(pack.name, config));
|
|
226
|
+
}
|
|
227
|
+
return dirs;
|
|
228
|
+
}
|
|
229
|
+
|
|
125
230
|
/**
|
|
126
231
|
* The document types whose compiled output a pass of this type reads.
|
|
127
232
|
*
|
|
@@ -258,11 +363,13 @@ async function generatePack(
|
|
|
258
363
|
config,
|
|
259
364
|
router,
|
|
260
365
|
routingReporter,
|
|
366
|
+
folderNotes,
|
|
367
|
+
corpus,
|
|
261
368
|
) {
|
|
262
369
|
const contentBase = config.paths.content;
|
|
263
370
|
const dest = packJsonDir(name, config);
|
|
264
371
|
|
|
265
|
-
const packClass =
|
|
372
|
+
const packClass = compilerFor(type, system ?? null);
|
|
266
373
|
if (!packClass) {
|
|
267
374
|
log.error(
|
|
268
375
|
`Pack ${name}: no compiler for document type "${type}" — the ` +
|
|
@@ -274,15 +381,55 @@ async function generatePack(
|
|
|
274
381
|
log.info(`Pack ${name}: ${contentBase} → ${dest}`);
|
|
275
382
|
|
|
276
383
|
let folderList;
|
|
277
|
-
let
|
|
384
|
+
let yamlResolver;
|
|
278
385
|
try {
|
|
279
386
|
folderList = folders ? loadFolders(path.join(contentBase, folders)) : [];
|
|
280
|
-
({ resolver } = buildFolderResolver(folderList));
|
|
387
|
+
({ resolver: yamlResolver } = buildFolderResolver(folderList));
|
|
281
388
|
} catch (err) {
|
|
282
389
|
log.error(`${name} ${folders} validation failed: ${err.message}`);
|
|
283
390
|
return { errors: 1, compiled: 0 };
|
|
284
391
|
}
|
|
285
392
|
|
|
393
|
+
// Which folder notes this pack turned out to hold something for. A folder
|
|
394
|
+
// materialises in every pack holding a document that references it, so the
|
|
395
|
+
// set is not knowable until the pass has compiled — which is why these
|
|
396
|
+
// documents are written after `compile()` and the YAML ones before it
|
|
397
|
+
// (#257).
|
|
398
|
+
/** @type {Set<import("./folder-notes.mjs").FolderNote>} */
|
|
399
|
+
const referencedFolders = new Set();
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* The Foundry folder id a note names, by address or by id.
|
|
403
|
+
*
|
|
404
|
+
* The two spellings resolve against two different sources and always did:
|
|
405
|
+
* `packFolder` names a folder **note**, resolved through the address index
|
|
406
|
+
* shared by the whole build, and `folder` names a Foundry **id** declared
|
|
407
|
+
* in this pack's own YAML. Which one applies is the field the value was
|
|
408
|
+
* written in, never the string (#251).
|
|
409
|
+
*
|
|
410
|
+
* @param {string|null|undefined} value - As authored.
|
|
411
|
+
* @param {object} [opts]
|
|
412
|
+
* @param {boolean} [opts.isAddress] - Whether `value` is a folder address.
|
|
413
|
+
* @returns {string|null} The folder id, or `null` for an absent value.
|
|
414
|
+
*/
|
|
415
|
+
const resolver = (value, { isAddress = false } = {}) => {
|
|
416
|
+
if (value == null || value === "") return null;
|
|
417
|
+
if (!isAddress) return yamlResolver(value);
|
|
418
|
+
const folder = folderNotes.resolve(value);
|
|
419
|
+
// Its ancestors with it: a `Folder` whose parent is absent from the
|
|
420
|
+
// pack is an orphan Foundry renders at the root, so materialising a
|
|
421
|
+
// folder without its chain breaks the tree at the top rather than
|
|
422
|
+
// merely leaving it incomplete.
|
|
423
|
+
referencedFolders.add(folder);
|
|
424
|
+
// Asked of *this* pack: a folder's hierarchy is per-pack even though
|
|
425
|
+
// its identity is not, so the chain that has to come with it is the
|
|
426
|
+
// chain it has here.
|
|
427
|
+
for (const ancestor of folderNotes.ancestorsOf(folder, name)) {
|
|
428
|
+
referencedFolders.add(ancestor);
|
|
429
|
+
}
|
|
430
|
+
return folder.id;
|
|
431
|
+
};
|
|
432
|
+
|
|
286
433
|
// Wipe and recreate so removed content notes leave no stale JSON.
|
|
287
434
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
288
435
|
fs.mkdirSync(dest, { recursive: true });
|
|
@@ -304,6 +451,8 @@ async function generatePack(
|
|
|
304
451
|
const pack = new packClass({
|
|
305
452
|
contentBase,
|
|
306
453
|
dest,
|
|
454
|
+
// The corpus this compile derived once, shared by every pass (#243).
|
|
455
|
+
corpus,
|
|
307
456
|
companionDests,
|
|
308
457
|
// The actors pass resolves each being's embedded items against the items
|
|
309
458
|
// passes' output. That used to be an unwritten sibling-directory contract
|
|
@@ -320,7 +469,15 @@ async function generatePack(
|
|
|
320
469
|
// cache throws naming `content-build deps fetch` rather than
|
|
321
470
|
// downloading inside a compile.
|
|
322
471
|
foreignSourceDirs: foreignItemCatalogDirs(config),
|
|
472
|
+
// The bundles pass resolves each Adventure's members against the output
|
|
473
|
+
// of every pass that produces one. Stated from the configured pack list
|
|
474
|
+
// for the same reason `itemsSourceDirs` is (#1508), and scoped to this
|
|
475
|
+
// pack's system so a bundle holds the catalogue it is compiled for.
|
|
476
|
+
bundleSourceDirs: bundleSourceJsonDirs(config, system ?? null),
|
|
323
477
|
folderResolver: resolver,
|
|
478
|
+
// One answer to "which files are the corpus?", from the configuration
|
|
479
|
+
// this build resolved rather than from the working directory (#243).
|
|
480
|
+
skipDirectories: config.skipDirectories,
|
|
324
481
|
packName: name,
|
|
325
482
|
// Which system this pack's documents are stamped for (#48).
|
|
326
483
|
packSystem: system ?? null,
|
|
@@ -329,9 +486,60 @@ async function generatePack(
|
|
|
329
486
|
routingReporter,
|
|
330
487
|
});
|
|
331
488
|
await pack.compile();
|
|
489
|
+
|
|
490
|
+
// After the pass, because only now is it known what this pack references.
|
|
491
|
+
// One folder note materialises in several packs — the items pack and the
|
|
492
|
+
// journals pack both hold it when both hold something filed in it — and
|
|
493
|
+
// every copy carries the same `_id`, which is what files a documentation
|
|
494
|
+
// journal beside the item it describes rather than in a folder that merely
|
|
495
|
+
// looks alike (#257).
|
|
496
|
+
writeFolderNoteDocs(
|
|
497
|
+
referencedFolders,
|
|
498
|
+
folderNotes,
|
|
499
|
+
statsForPack(system, config),
|
|
500
|
+
dest,
|
|
501
|
+
type,
|
|
502
|
+
name,
|
|
503
|
+
);
|
|
504
|
+
|
|
332
505
|
return { errors: pack.errorCount, compiled: pack.compiledCount };
|
|
333
506
|
}
|
|
334
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Write one `Folder` document per referenced folder note into a pack.
|
|
510
|
+
*
|
|
511
|
+
* Emitted in address order rather than in the order the pass happened to
|
|
512
|
+
* reference them, so the same tree compiles to the same bytes on every run.
|
|
513
|
+
*
|
|
514
|
+
* @param {Set<import("./folder-notes.mjs").FolderNote>} referenced - The folder
|
|
515
|
+
* notes this pack holds something for, ancestors included.
|
|
516
|
+
* @param {object} folderNotes - The folder-note index.
|
|
517
|
+
* @param {object} stats - The `_stats` block every emitted document carries.
|
|
518
|
+
* @param {string} dest - The pack's JSON directory.
|
|
519
|
+
* @param {string} documentType - The document class the pack holds.
|
|
520
|
+
* @param {string} packName - Which pack, so each folder takes the parent it has
|
|
521
|
+
* *here* rather than the one it has by default.
|
|
522
|
+
* @returns {void}
|
|
523
|
+
*/
|
|
524
|
+
function writeFolderNoteDocs(referenced, folderNotes, stats, dest, documentType, packName) {
|
|
525
|
+
if (referenced.size === 0) return;
|
|
526
|
+
const ordered = [...referenced].sort((a, b) => (a.address < b.address ? -1 : 1));
|
|
527
|
+
for (const folder of ordered) {
|
|
528
|
+
const doc = folderDocument(
|
|
529
|
+
folder,
|
|
530
|
+
folderNotes.parentOf(folder, packName),
|
|
531
|
+
documentType,
|
|
532
|
+
stats,
|
|
533
|
+
);
|
|
534
|
+
fs.writeFileSync(
|
|
535
|
+
path.join(dest, folderFilename(folder.name, folder.id)),
|
|
536
|
+
JSON.stringify(doc, null, 2),
|
|
537
|
+
"utf8",
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
log.info(`Emitted ${ordered.length} folder-note document(s) to ${dest}`);
|
|
541
|
+
}
|
|
542
|
+
|
|
335
543
|
/**
|
|
336
544
|
* The passes that compiled nothing when they were expected to compile
|
|
337
545
|
* something — a build failure, not a quiet no-op.
|
|
@@ -399,6 +607,33 @@ export async function generatePacksJson({ only, config = loadPackConfig() } = {}
|
|
|
399
607
|
}
|
|
400
608
|
log.info(`Content tree: ${noteCount} note(s) at ${contentBase}`);
|
|
401
609
|
|
|
610
|
+
// One router per configuration, so every pass agrees about where a note
|
|
611
|
+
// goes, and the first pack of each document type owns the error message for
|
|
612
|
+
// a note of that type that goes nowhere. Resolved here because the corpus
|
|
613
|
+
// below is derived against it, and the corpus is what every reader from
|
|
614
|
+
// this point on reads (#243).
|
|
615
|
+
const router = routerFor(config);
|
|
616
|
+
|
|
617
|
+
// The corpus every pass runs over, and the three whole-tree indexes built
|
|
618
|
+
// over it, derived **once** for the whole compile (#243). Each is a pure
|
|
619
|
+
// function of (tree, scope, router), none of which varies between passes —
|
|
620
|
+
// `router` is one object, handed to all of them — so the passes were
|
|
621
|
+
// deriving the same answers over and over. Compiling `sohl` read every note
|
|
622
|
+
// twenty times before this: four per pass, five passes.
|
|
623
|
+
//
|
|
624
|
+
// Derived here, before the first reader: the unclaimed-type check below is
|
|
625
|
+
// one, and a check that walked the tree itself would be answering about a
|
|
626
|
+
// different corpus from the one the passes then compile.
|
|
627
|
+
const corpusProblems = [];
|
|
628
|
+
const corpus = await buildCompileCorpus({
|
|
629
|
+
contentBase,
|
|
630
|
+
skipDirectories: config.skipDirectories,
|
|
631
|
+
router,
|
|
632
|
+
config,
|
|
633
|
+
problems: corpusProblems,
|
|
634
|
+
});
|
|
635
|
+
for (const problem of corpusProblems) emitDiagnostic(problem);
|
|
636
|
+
|
|
402
637
|
// A note whose `type:` no configured pack claims compiles into nothing, and
|
|
403
638
|
// used to say nothing (#146) — no pass got far enough to reject it, so the
|
|
404
639
|
// silence had no owner. Asked once, of the whole configuration, because
|
|
@@ -406,23 +641,77 @@ export async function generatePacksJson({ only, config = loadPackConfig() } = {}
|
|
|
406
641
|
// every type a system deliberately does not map, which is exactly the
|
|
407
642
|
// silence #79 requires. Independent of `only`, since it is a fact about the
|
|
408
643
|
// configured pack list rather than about which passes this run executes.
|
|
409
|
-
const unclaimed = unclaimedNoteFindings(config);
|
|
644
|
+
const unclaimed = unclaimedNoteFindings(config, undefined, { records: corpus.records });
|
|
410
645
|
for (const finding of unclaimed) emitDiagnostic(finding);
|
|
411
646
|
|
|
412
647
|
fs.mkdirSync(config.paths.packJson, { recursive: true });
|
|
413
648
|
|
|
414
649
|
// A companion pack has no pass of its own — naming it selects the pass that
|
|
415
650
|
// writes it, so `compile adventures` is not a silent no-op.
|
|
651
|
+
//
|
|
652
|
+
// A **prebuilt** pack has no pass either, and for a plainer reason: its
|
|
653
|
+
// per-document JSON is checked in. Passed over rather than compiled — which
|
|
654
|
+
// it could not be before #259, since the only prebuilt pack in the wild
|
|
655
|
+
// holds Adventures and no compiler was registered for that document type,
|
|
656
|
+
// so the pack failed the build with "no compiler for document type". Now
|
|
657
|
+
// one is registered, and running it would wipe `build/packs-json/<name>/`
|
|
658
|
+
// and write nothing into it — then report the empty pass as an error.
|
|
416
659
|
const packs = config.packs.filter(
|
|
417
660
|
(pack) =>
|
|
418
|
-
!
|
|
419
|
-
|
|
420
|
-
|
|
661
|
+
!pack.prebuilt &&
|
|
662
|
+
(!only ||
|
|
663
|
+
pack.name === only ||
|
|
664
|
+
pack.companions.some((companion) => companion.name === only)),
|
|
421
665
|
);
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
// a
|
|
425
|
-
|
|
666
|
+
// Built once for the whole build, not once per pack: a folder note is one
|
|
667
|
+
// definition with one address, and every pass resolves against the same
|
|
668
|
+
// index. A dangling `parent` or a parent cycle is therefore reported once,
|
|
669
|
+
// as a fact about the tree, rather than once per pass that happened to walk
|
|
670
|
+
// it (#256).
|
|
671
|
+
let folderNotes;
|
|
672
|
+
try {
|
|
673
|
+
folderNotes = buildFolderNoteIndex(
|
|
674
|
+
collectFolderNotes(
|
|
675
|
+
// The corpus this compile derived picks the notes (#243); the
|
|
676
|
+
// file supplies their frontmatter, and this is one of the few
|
|
677
|
+
// places where that distinction is load-bearing rather than
|
|
678
|
+
// incidental.
|
|
679
|
+
//
|
|
680
|
+
// `collectFolderNotes` treats `fm.id` as an **authored pin**,
|
|
681
|
+
// which wins over the id it derives under the folder namespace.
|
|
682
|
+
// A record's `id` is not that: the index fills it in for every
|
|
683
|
+
// addressable note (#270), so handing records straight over
|
|
684
|
+
// would make every folder look pinned and file each one under a
|
|
685
|
+
// different id than the packs address it by. The index cannot
|
|
686
|
+
// tell a pin from a derivation, so the note is read — and only
|
|
687
|
+
// folder notes are, 79 of `sohl`'s 1,685 rather than all of
|
|
688
|
+
// them.
|
|
689
|
+
corpus.records
|
|
690
|
+
.filter(
|
|
691
|
+
(record) =>
|
|
692
|
+
isNoteRecord(record) &&
|
|
693
|
+
String(record.type ?? "").toLowerCase() === FOLDER_TYPE,
|
|
694
|
+
)
|
|
695
|
+
.map((record) => {
|
|
696
|
+
const absPath = noteFile(contentBase, record);
|
|
697
|
+
return { frontmatter: parseMarkdownFile(absPath).frontmatter, absPath };
|
|
698
|
+
}),
|
|
699
|
+
// The package this build resolved, not the ambient accessor:
|
|
700
|
+
// they are the same value in a real repository and different
|
|
701
|
+
// ones under `PACKAGE_BUILD_CONFIG`, in a worktree, or in a
|
|
702
|
+
// test (#243).
|
|
703
|
+
config.contentPackage,
|
|
704
|
+
),
|
|
705
|
+
);
|
|
706
|
+
} catch (err) {
|
|
707
|
+
emitDiagnostic({
|
|
708
|
+
file: err.absPath ?? contentBase,
|
|
709
|
+
severity: "error",
|
|
710
|
+
message: err.message,
|
|
711
|
+
});
|
|
712
|
+
return 1;
|
|
713
|
+
}
|
|
714
|
+
|
|
426
715
|
const firstOfType = new Map();
|
|
427
716
|
for (const pack of config.packs) {
|
|
428
717
|
if (!firstOfType.has(pack.type)) firstOfType.set(pack.type, pack.name);
|
|
@@ -450,7 +739,7 @@ export async function generatePacksJson({ only, config = loadPackConfig() } = {}
|
|
|
450
739
|
return unsatisfied.length + unclaimed.length;
|
|
451
740
|
}
|
|
452
741
|
|
|
453
|
-
let totalErrors = unclaimed.length;
|
|
742
|
+
let totalErrors = unclaimed.length + corpusProblems.length;
|
|
454
743
|
const passes = [];
|
|
455
744
|
for (const pack of ordered) {
|
|
456
745
|
const { errors, compiled } = await generatePack(
|
|
@@ -458,6 +747,8 @@ export async function generatePacksJson({ only, config = loadPackConfig() } = {}
|
|
|
458
747
|
config,
|
|
459
748
|
router,
|
|
460
749
|
firstOfType.get(pack.type) === pack.name,
|
|
750
|
+
folderNotes,
|
|
751
|
+
corpus,
|
|
461
752
|
);
|
|
462
753
|
totalErrors += errors;
|
|
463
754
|
passes.push({
|