@heroiclands/package-build 20.3.1 → 20.5.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 +351 -0
- package/CONTENT.md +322 -110
- package/README.md +21 -10
- package/bin/content-build.mjs +204 -92
- package/bin/package-build.mjs +48 -15
- package/config.mjs +6 -8
- package/content-config.mjs +518 -151
- package/docs/api.md +1352 -0
- package/docs/commands.md +1609 -0
- package/docs/configuration.md +1432 -0
- package/docs/content-format.md +73 -74
- package/docs/diagnostics.md +356 -0
- package/docs/getting-started.md +813 -0
- package/docs/project-setup.md +469 -0
- package/e2e.mjs +4 -4
- package/engine/actor-compiler.mjs +64 -61
- package/engine/address-charset.mjs +6 -6
- package/engine/address-diff.mjs +57 -52
- package/engine/anchored-sections.mjs +1 -1
- package/engine/anchors.mjs +1 -1
- package/engine/base-compiler.mjs +42 -38
- package/engine/bundle-notes.mjs +4 -4
- package/engine/bundles.mjs +17 -8
- package/engine/code-fences.mjs +3 -3
- package/engine/compendiums.mjs +8 -8
- package/engine/compile-corpus.mjs +4 -5
- package/engine/content-address.mjs +24 -24
- package/engine/content-charset.mjs +1 -1
- package/engine/content-format-check.mjs +24 -25
- package/engine/content-format.mjs +8 -8
- package/engine/content-html.mjs +202 -0
- package/engine/content-icons.mjs +196 -165
- package/engine/content-index.mjs +61 -40
- package/engine/content-links.mjs +74 -41
- package/engine/content-lint.mjs +64 -34
- package/engine/content-package.mjs +7 -9
- package/engine/content-slug.mjs +2 -2
- package/engine/content-tables.mjs +38 -34
- package/engine/content-tree.mjs +1 -1
- package/engine/derived-fields.mjs +174 -0
- package/engine/diagnostics.mjs +3 -4
- package/engine/document-subtypes.mjs +6 -7
- package/engine/field-reference.mjs +8 -8
- package/engine/field-spec.mjs +29 -30
- package/engine/folder-notes.mjs +18 -16
- package/engine/foreign-catalog.mjs +8 -9
- package/engine/foundry-entries.mjs +19 -20
- package/engine/frontmatter-lint.mjs +95 -103
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +44 -39
- package/engine/helpers.mjs +147 -84
- package/engine/homepage.mjs +20 -22
- package/engine/ids.mjs +15 -15
- package/engine/index-records.mjs +4 -4
- package/engine/index.mjs +26 -10
- package/engine/item-compiler.mjs +54 -29
- package/engine/item-docs.mjs +11 -13
- package/engine/item-registry.mjs +13 -13
- package/engine/journals.mjs +24 -15
- package/engine/macros.mjs +3 -3
- package/engine/map-notes.mjs +6 -6
- package/engine/metadata-index.mjs +5 -5
- package/engine/note-claims.mjs +51 -41
- package/engine/note-ids.mjs +5 -5
- package/engine/note-package.mjs +4 -4
- package/engine/note-renames.mjs +3 -3
- package/engine/note-schemas.mjs +3 -8
- package/engine/note-vocabulary.mjs +56 -54
- package/engine/pack-config.mjs +39 -25
- package/engine/pack-router.mjs +0 -0
- package/engine/pdf-build.mjs +464 -0
- package/engine/pdf-fonts.mjs +420 -0
- package/engine/pdf-render.mjs +798 -0
- package/engine/pdf-toc.mjs +525 -0
- package/engine/prose-config.mjs +9 -9
- package/engine/prose-lint.mjs +12 -13
- package/engine/region-events.mjs +2 -2
- package/engine/retired-fields.mjs +24 -22
- package/engine/runtime-only-fields.mjs +1 -1
- package/engine/scene-levels.mjs +1 -1
- package/engine/scenes.mjs +33 -25
- package/engine/schema-check.mjs +21 -21
- package/engine/schema-extract.mjs +1 -1
- package/engine/site-build.mjs +66 -50
- package/engine/site-index.mjs +17 -18
- package/engine/sql-tables.mjs +21 -13
- package/engine/subtype-registry.mjs +5 -4
- package/engine/system-block.mjs +18 -19
- package/engine/systems.mjs +2 -2
- package/engine/web-wikilinks.mjs +41 -38
- package/engine/wikilink-syntax.mjs +16 -16
- package/engine/wikilinks.mjs +40 -42
- package/hm3/actors.mjs +30 -22
- package/hm3/document-subtypes.mjs +5 -5
- package/hm3/index.mjs +2 -2
- package/hm3/item-builders.mjs +1 -1
- package/hm3/item-fields.mjs +3 -3
- package/hm3/items.mjs +28 -8
- package/hm3/template-priority.mjs +2 -2
- package/lang.mjs +3 -3
- package/manifest.mjs +17 -20
- package/package.json +1 -2
- package/release.mjs +66 -6
- package/sohl/actors.mjs +36 -28
- package/sohl/affiliation-standings.mjs +2 -2
- package/sohl/being-info.mjs +5 -5
- package/sohl/default-item-art.mjs +5 -5
- package/sohl/document-subtypes.mjs +5 -5
- package/sohl/index.mjs +3 -3
- package/sohl/item-builders.mjs +7 -7
- package/sohl/item-fields.mjs +8 -9
- package/sohl/items.mjs +28 -6
- package/sohl/kb-passes.mjs +5 -5
- package/sohl/note-schemas.mjs +12 -12
- package/sohl/skill-base.mjs +1 -1
- package/types/content-config.d.mts +91 -39
- package/types/e2e.d.mts +3 -3
- package/types/engine/actor-compiler.d.mts +45 -40
- package/types/engine/address-charset.d.mts +6 -6
- package/types/engine/address-diff.d.mts +60 -6
- package/types/engine/base-compiler.d.mts +27 -21
- package/types/engine/bundle-notes.d.mts +3 -3
- package/types/engine/bundles.d.mts +10 -1
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/compendiums.d.mts +3 -3
- package/types/engine/compile-corpus.d.mts +1 -1
- package/types/engine/content-address.d.mts +20 -20
- package/types/engine/content-format-check.d.mts +6 -6
- package/types/engine/content-format.d.mts +2 -2
- package/types/engine/content-html.d.mts +78 -0
- package/types/engine/content-icons.d.mts +139 -92
- package/types/engine/content-index.d.mts +59 -15
- package/types/engine/content-links.d.mts +8 -8
- package/types/engine/content-lint.d.mts +8 -6
- package/types/engine/content-package.d.mts +6 -8
- package/types/engine/content-tables.d.mts +49 -18
- package/types/engine/derived-fields.d.mts +101 -0
- package/types/engine/diagnostics.d.mts +2 -2
- package/types/engine/document-subtypes.d.mts +3 -3
- package/types/engine/field-spec.d.mts +37 -39
- package/types/engine/folder-notes.d.mts +4 -5
- package/types/engine/foreign-catalog.d.mts +3 -3
- package/types/engine/foundry-entries.d.mts +6 -7
- package/types/engine/frontmatter-lint.d.mts +10 -9
- package/types/engine/frontmatter.d.mts +7 -7
- package/types/engine/generate.d.mts +7 -7
- package/types/engine/helpers.d.mts +128 -56
- package/types/engine/homepage.d.mts +16 -18
- package/types/engine/ids.d.mts +13 -13
- package/types/engine/index-records.d.mts +3 -3
- package/types/engine/index.d.mts +6 -0
- package/types/engine/item-compiler.d.mts +21 -5
- package/types/engine/item-docs.d.mts +2 -2
- package/types/engine/item-registry.d.mts +6 -6
- package/types/engine/journals.d.mts +12 -3
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/metadata-index.d.mts +4 -4
- package/types/engine/note-claims.d.mts +30 -22
- package/types/engine/note-ids.d.mts +4 -4
- package/types/engine/note-package.d.mts +1 -1
- package/types/engine/note-renames.d.mts +3 -3
- package/types/engine/note-vocabulary.d.mts +31 -203
- package/types/engine/pack-config.d.mts +7 -7
- package/types/engine/pack-router.d.mts +1 -1
- package/types/engine/pdf-build.d.mts +42 -0
- package/types/engine/pdf-fonts.d.mts +30 -0
- package/types/engine/pdf-render.d.mts +144 -0
- package/types/engine/pdf-toc.d.mts +114 -0
- package/types/engine/prose-config.d.mts +9 -9
- package/types/engine/prose-lint.d.mts +3 -4
- package/types/engine/region-events.d.mts +2 -2
- package/types/engine/retired-fields.d.mts +10 -9
- package/types/engine/scenes.d.mts +10 -1
- package/types/engine/schema-check.d.mts +13 -13
- package/types/engine/site-build.d.mts +52 -25
- package/types/engine/site-index.d.mts +3 -4
- package/types/engine/sql-tables.d.mts +11 -5
- package/types/engine/subtype-registry.d.mts +3 -3
- package/types/engine/system-block.d.mts +3 -3
- package/types/engine/web-wikilinks.d.mts +7 -7
- package/types/engine/wikilink-syntax.d.mts +17 -17
- package/types/engine/wikilinks.d.mts +14 -14
- package/types/hm3/actors.d.mts +9 -1
- package/types/hm3/document-subtypes.d.mts +1 -1
- package/types/hm3/items.d.mts +9 -2
- package/types/hm3/template-priority.d.mts +1 -1
- package/types/manifest.d.mts +8 -8
- package/types/release.d.mts +15 -4
- package/types/sohl/actors.d.mts +12 -4
- package/types/sohl/affiliation-standings.d.mts +2 -2
- package/types/sohl/being-info.d.mts +2 -2
- package/types/sohl/document-subtypes.d.mts +1 -1
- package/types/sohl/items.d.mts +9 -2
- package/types/sohl/note-schemas.d.mts +1 -1
- package/MIGRATING.md +0 -608
package/engine/content-index.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Emitting this package's content index
|
|
15
|
+
* Emitting this package's content index.
|
|
16
16
|
*
|
|
17
17
|
* Every content build already walks the whole note tree and parses every note's
|
|
18
18
|
* frontmatter — the pack compilers, the site build, and the content-table
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* "which beings carry no `kbcat`?", "what does this table actually select?",
|
|
22
22
|
* "did that type rename leave anything behind?" have no answer short of writing
|
|
23
23
|
* a throwaway script that re-walks the tree. Eight dead Bestiary tables shipped
|
|
24
|
-
* for weeks behind exactly that gap
|
|
24
|
+
* for weeks behind exactly that gap.
|
|
25
25
|
*
|
|
26
26
|
* This module publishes the walk. One line of JSON per note, in
|
|
27
27
|
* [JSON Lines](https://jsonlines.org/) — the whole frontmatter, plus where the
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
* to every player, and a build artifact has no business there.
|
|
63
63
|
*
|
|
64
64
|
* **What it deliberately does not carry: the note's text, and positions within
|
|
65
|
-
* it.**
|
|
65
|
+
* it.** The question is whether the index should record a position for every
|
|
66
66
|
* frontmatter key, so that a pass reading the index could report a field defect
|
|
67
67
|
* without opening the file. It should not, and the numbers are not close: over
|
|
68
68
|
* `sohl`'s 1,685 notes the index is 3.0 MB and holds 50,598 leaf values, so a
|
|
@@ -94,7 +94,7 @@ import unidecode from "unidecode";
|
|
|
94
94
|
import { metadataFileName } from "./metadata-index.mjs";
|
|
95
95
|
import { addressSlug, canonicalKey } from "./content-address.mjs";
|
|
96
96
|
// One reader for a note's anchors, shared with the link checker and with the
|
|
97
|
-
// builds that emit a link
|
|
97
|
+
// builds that emit a link. Re-exported because this is where callers
|
|
98
98
|
// have always addressed it.
|
|
99
99
|
import { collectAnchors } from "./anchors.mjs";
|
|
100
100
|
import { subtypeRow, NO_SYSTEM, systemOf } from "./document-subtypes.mjs";
|
|
@@ -105,7 +105,7 @@ import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./note-claims.mjs";
|
|
|
105
105
|
*
|
|
106
106
|
* The specification's word, not this module's: the canonical address carries it
|
|
107
107
|
* in the same position — `harnadventures-none-being-grod` — so the index and
|
|
108
|
-
* the address say "no system" the same way
|
|
108
|
+
* the address say "no system" the same way.
|
|
109
109
|
*
|
|
110
110
|
* @type {string}
|
|
111
111
|
*/
|
|
@@ -115,12 +115,12 @@ import { entriesForNote, foundryIdentities } from "./foundry-entries.mjs";
|
|
|
115
115
|
import { walkMarkdownTree } from "./helpers.mjs";
|
|
116
116
|
import { resolveNoteId } from "./note-ids.mjs";
|
|
117
117
|
// The retired-field refusal and the key locator, so a note authoring a derived
|
|
118
|
-
// key is reported where it is rather than as a bare abort
|
|
118
|
+
// key is reported where it is rather than as a bare abort.
|
|
119
119
|
import { assertNoDeclaredPackage } from "./note-package.mjs";
|
|
120
120
|
import { locateFrontmatterKey } from "./retired-fields.mjs";
|
|
121
121
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
122
122
|
// The record accessors, which live apart so that a module the compilers load
|
|
123
|
-
// can read a record without importing this one and closing a cycle
|
|
123
|
+
// can read a record without importing this one and closing a cycle.
|
|
124
124
|
// Re-exported because this is where callers have always addressed them.
|
|
125
125
|
import { authoredFrontmatter, DERIVED_KEYS, isNoteRecord, noteFile } from "./index-records.mjs";
|
|
126
126
|
|
|
@@ -259,19 +259,6 @@ export function asciiAliases(aliases) {
|
|
|
259
259
|
return aliases.map((alias) => asciiName(alias)).filter((alias) => alias !== null);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
/**
|
|
263
|
-
* Build one index record from a note's frontmatter and its place in the tree.
|
|
264
|
-
*
|
|
265
|
-
* @param {object} options - Options.
|
|
266
|
-
* @param {Record<string, any>} options.frontmatter - The note's parsed frontmatter.
|
|
267
|
-
* @param {string} options.relPath - Its path below the content root, POSIX-separated.
|
|
268
|
-
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
269
|
-
* @param {string} [options.body] - The note's markdown body, for its anchors.
|
|
270
|
-
* @param {number} [options.bodyLine] - The 1-based file line the body starts on.
|
|
271
|
-
* @returns {Record<string, any>} The record, keys sorted at every depth.
|
|
272
|
-
* @throws {Error} When the note carries a key this module derives, which would
|
|
273
|
-
* otherwise be overwritten without a word.
|
|
274
|
-
*/
|
|
275
262
|
/**
|
|
276
263
|
* This note's Foundry addresses, or `null` where it has none.
|
|
277
264
|
*
|
|
@@ -281,7 +268,7 @@ export function asciiAliases(aliases) {
|
|
|
281
268
|
* plus the pack router — frontmatter and configuration, nothing from a compiled
|
|
282
269
|
* pack — so the index's frontmatter walk already has every input. Deriving it
|
|
283
270
|
* twice is how two artifacts describing one note start disagreeing, which is
|
|
284
|
-
* the failure the merge is meant to end
|
|
271
|
+
* the failure the merge is meant to end.
|
|
285
272
|
*
|
|
286
273
|
* The shape flattens the manifest's *two* entries for an item note onto the one
|
|
287
274
|
* record the index keeps per note. An item compiles into a document **and** a
|
|
@@ -339,7 +326,7 @@ function foundryEntries({ frontmatter, address, body, manifest }) {
|
|
|
339
326
|
* shipped map produced and said nothing about the other.
|
|
340
327
|
*
|
|
341
328
|
* Only `sohl` can appear today, because `KNOWN_DOCUMENT_SUBTYPE_MAPS` holds one
|
|
342
|
-
* map
|
|
329
|
+
* map, the `hm3/` half being separate. The shape is system-keyed so
|
|
343
330
|
* that adding it is one more key rather than a second breaking change to an
|
|
344
331
|
* artifact consumers have already started reading.
|
|
345
332
|
*
|
|
@@ -369,7 +356,7 @@ function foundryBlock(entry, system) {
|
|
|
369
356
|
* Refuse a note that authors a key the index derives, and say where.
|
|
370
357
|
*
|
|
371
358
|
* **Located, because every reader of the index is now a reporter of this.**
|
|
372
|
-
*
|
|
359
|
+
* With the emitter the only pass building a record, aborting
|
|
373
360
|
* with a bare message was the whole story. Now the link check and the address
|
|
374
361
|
* diff read the index too, and a bare abort in one of them reports *nothing*
|
|
375
362
|
* about the tree — the one malformed note takes every other finding with it,
|
|
@@ -378,7 +365,7 @@ function foundryBlock(entry, system) {
|
|
|
378
365
|
* can emit `file:line:column: error: …` like any other finding.
|
|
379
366
|
*
|
|
380
367
|
* **`package:` keeps its own words.** It is not a name collision but a *retired
|
|
381
|
-
* field
|
|
368
|
+
* field*, and the correction is to delete it, not to rename it — which is
|
|
382
369
|
* what {@link module:engine/note-package.assertNoDeclaredPackage} has always
|
|
383
370
|
* said, and had no caller to say it to. Deferring to it means one message for
|
|
384
371
|
* one mistake rather than two that contradict each other about the fix.
|
|
@@ -410,6 +397,29 @@ function assertNoDerivedKeys(frontmatter, relPath, absPath, contentPackage) {
|
|
|
410
397
|
}
|
|
411
398
|
}
|
|
412
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Build one index record from a note's frontmatter and its place in the tree.
|
|
402
|
+
*
|
|
403
|
+
* The frontmatter as authored, plus what the index derives from it: the package
|
|
404
|
+
* it compiles as, its address, ASCII folds of its name and aliases, the anchors
|
|
405
|
+
* of its body, its Foundry block, the address of its documentation journal, and
|
|
406
|
+
* where the file sits within the tree.
|
|
407
|
+
*
|
|
408
|
+
* @param {object} options - Options.
|
|
409
|
+
* @param {Record<string, any>} options.frontmatter - The note's parsed frontmatter.
|
|
410
|
+
* @param {string} options.relPath - Its path below the content root, POSIX-separated.
|
|
411
|
+
* @param {string} [options.absPath] - The file, read only on the failing path to
|
|
412
|
+
* locate the offending key.
|
|
413
|
+
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
414
|
+
* @param {string} [options.body] - The note's markdown body, for its anchors.
|
|
415
|
+
* @param {number} [options.bodyLine] - The 1-based file line the body starts on.
|
|
416
|
+
* @param {object} [options.manifest] - The package manifest, which the Foundry
|
|
417
|
+
* entries are derived against.
|
|
418
|
+
* @returns {Record<string, any>} The record, keys sorted at every depth.
|
|
419
|
+
* @throws {Error} When the note carries a key this module derives, which would
|
|
420
|
+
* otherwise be overwritten without a word. `file` and, where the file was
|
|
421
|
+
* read, `position` ride on the error.
|
|
422
|
+
*/
|
|
413
423
|
export function buildIndexRecord({
|
|
414
424
|
frontmatter,
|
|
415
425
|
relPath,
|
|
@@ -466,16 +476,6 @@ export function buildIndexRecord({
|
|
|
466
476
|
);
|
|
467
477
|
}
|
|
468
478
|
|
|
469
|
-
/**
|
|
470
|
-
* Read a content tree into index records, in the order they will be written.
|
|
471
|
-
*
|
|
472
|
-
* @param {string} contentBase - The content tree to walk.
|
|
473
|
-
* @param {object} options - Options.
|
|
474
|
-
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
475
|
-
* @param {Array<string>} [options.skipDirectories] - Directory names to skip.
|
|
476
|
-
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
477
|
-
* not depend on directory-read order.
|
|
478
|
-
*/
|
|
479
479
|
/**
|
|
480
480
|
* The record for an item note's **documentation journal**.
|
|
481
481
|
*
|
|
@@ -517,7 +517,7 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
|
|
|
517
517
|
// The journal's own `_id`, taken from the entry rather than
|
|
518
518
|
// re-derived: every entry the index gives an identity to publishes
|
|
519
519
|
// both the id and the UUID, computed once by whatever owns that
|
|
520
|
-
// entry's derivation
|
|
520
|
+
// entry's derivation.
|
|
521
521
|
id: entry.id,
|
|
522
522
|
nameAscii: asciiName(frontmatter?.name?.full),
|
|
523
523
|
address: { slug: address.slug, canonical: entry.key },
|
|
@@ -531,13 +531,34 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
|
|
|
531
531
|
);
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Read a content tree into index records, in the order they will be written.
|
|
536
|
+
*
|
|
537
|
+
* An item note yields two records — the item, and the documentation journal
|
|
538
|
+
* that is a document in its own right.
|
|
539
|
+
*
|
|
540
|
+
* @param {string} contentBase - The content tree to walk.
|
|
541
|
+
* @param {object} options - Options.
|
|
542
|
+
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
543
|
+
* @param {readonly string[]} options.skipDirectories - The walk's scope, stated
|
|
544
|
+
* by the caller. An absent one is the caller's omission, and
|
|
545
|
+
* {@link module:engine/helpers.walkMarkdownTree} throws on it.
|
|
546
|
+
* @param {object} [options.manifest] - The package manifest, which the Foundry
|
|
547
|
+
* entries are derived against.
|
|
548
|
+
* @param {object[]} [options.problems] - Supplied by a **reader**: a note that
|
|
549
|
+
* cannot be recorded is pushed here as a diagnostic and skipped. Omitted, the
|
|
550
|
+
* note throws — the contract the emitter needs, since an index missing a note
|
|
551
|
+
* asserts that it does not exist.
|
|
552
|
+
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
553
|
+
* not depend on directory-read order.
|
|
554
|
+
*/
|
|
534
555
|
export function collectContentIndex(
|
|
535
556
|
contentBase,
|
|
536
557
|
{ contentPackage, skipDirectories, manifest, problems },
|
|
537
558
|
) {
|
|
538
559
|
const records = [];
|
|
539
560
|
// Passed through rather than defaulted away: an absent scope is the
|
|
540
|
-
// caller's omission, and `walkMarkdownTree` says so
|
|
561
|
+
// caller's omission, and `walkMarkdownTree` says so.
|
|
541
562
|
const walkOpts = { skipDirectories };
|
|
542
563
|
|
|
543
564
|
for (const { frontmatter, body, bodyLine, absPath } of walkMarkdownTree(
|
|
@@ -545,7 +566,7 @@ export function collectContentIndex(
|
|
|
545
566
|
walkOpts,
|
|
546
567
|
)) {
|
|
547
568
|
const fm = frontmatter ?? {};
|
|
548
|
-
// The id the note's document is filed under
|
|
569
|
+
// The id the note's document is filed under, resolved before
|
|
549
570
|
// the record is built so the index publishes the address *and* the id
|
|
550
571
|
// that address derives.
|
|
551
572
|
resolveNoteId(fm, { pkg: contentPackage });
|
|
@@ -579,7 +600,7 @@ export function collectContentIndex(
|
|
|
579
600
|
}
|
|
580
601
|
records.push(record);
|
|
581
602
|
|
|
582
|
-
// An item note is two documents, so it is two records
|
|
603
|
+
// An item note is two documents, so it is two records.
|
|
583
604
|
const doc = foundryEntries({
|
|
584
605
|
frontmatter: fm,
|
|
585
606
|
address: record.address,
|
|
@@ -637,7 +658,7 @@ export function serializeContentIndex(records) {
|
|
|
637
658
|
*
|
|
638
659
|
* The half of {@link emitContentIndex} that derives rather than emits, so a
|
|
639
660
|
* pass that needs the corpus in memory — a SQL content table, the link check,
|
|
640
|
-
* and in time every reader
|
|
661
|
+
* and in time every converted reader — builds it the same way the artifact
|
|
641
662
|
* is built, rather than by walking and parsing again with its own idea of the
|
|
642
663
|
* scope.
|
|
643
664
|
*
|
|
@@ -648,7 +669,7 @@ export function serializeContentIndex(records) {
|
|
|
648
669
|
* caller that resolved one of its own; defaults to the resolved
|
|
649
670
|
* configuration's. Stated separately from `config` because a caller that was
|
|
650
671
|
* *handed* a scope must be able to pass it on rather than have it silently
|
|
651
|
-
* replaced by the one its configuration happens to carry
|
|
672
|
+
* replaced by the one its configuration happens to carry.
|
|
652
673
|
* @param {object[]} [opts.problems] - Supplied by a **reader**: a note that
|
|
653
674
|
* cannot be recorded is pushed here as a diagnostic and skipped, instead of
|
|
654
675
|
* aborting the derivation. Omitted, the note throws — which is the contract
|
package/engine/content-links.mjs
CHANGED
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
* written target is a *partial* address — the segments it omits are
|
|
28
28
|
* wildcards, and the package it omits is this one — so a target resolving to
|
|
29
29
|
* *several* notes is an ambiguity rather than a first match, and is reported
|
|
30
|
-
* naming every candidate
|
|
30
|
+
* naming every candidate.
|
|
31
31
|
* 3. **An unlabelled link.** `[[x]]` addresses nothing: the alias namespace it
|
|
32
|
-
*
|
|
32
|
+
* once named is retired, and a shortcode is an address rather than
|
|
33
33
|
* prose, so the link has neither a resolvable target nor text to show. The
|
|
34
34
|
* correction is always `[[type-shortcode|Text]]`.
|
|
35
35
|
* 4. **A wikilink authored in frontmatter.** Both builds walk a note's *body*
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
* document is reachable from the book's root" — is a statement about what one
|
|
50
50
|
* package publishes, not about the note format, so it belongs with the
|
|
51
51
|
* publishing it describes; so does a retired hostname. Both are served by the
|
|
52
|
-
* link graph returned here rather than implemented here
|
|
52
|
+
* link graph returned here rather than implemented here.
|
|
53
53
|
*
|
|
54
54
|
* @module
|
|
55
55
|
*/
|
|
@@ -61,7 +61,7 @@ import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
|
61
61
|
import { expandContentTables } from "./content-tables.mjs";
|
|
62
62
|
import { collectAnchors } from "./anchors.mjs";
|
|
63
63
|
// The corpus, and everything derived from it, read from the one place that
|
|
64
|
-
// derives it
|
|
64
|
+
// derives it. Nothing in the index's own import graph reaches this
|
|
65
65
|
// module, so this is a plain static import rather than the deferred one
|
|
66
66
|
// `sql-tables` needs to keep out of the compilers' cycle.
|
|
67
67
|
import { authoredFrontmatter, indexRecordsFor, isNoteRecord, noteFile } from "./content-index.mjs";
|
|
@@ -88,7 +88,7 @@ import { readQualifier } from "./wikilinks.mjs";
|
|
|
88
88
|
* Every `{#anchor}` a note declares on a heading.
|
|
89
89
|
*
|
|
90
90
|
* **Read from the content index's reader, not a second one.** This module kept
|
|
91
|
-
* its own
|
|
91
|
+
* its own, and the two disagreed: it matched `{#([a-z0-9-]+)}` while
|
|
92
92
|
* {@link module:engine/content-index.collectAnchors} matches `{#([^}]+)}`, so
|
|
93
93
|
* an anchor with a capital in it — `{#CalendarFormat}` — existed for the index
|
|
94
94
|
* and for the compiler and did not exist for the link checker. Nothing links to
|
|
@@ -110,7 +110,7 @@ export function anchorsOf(body) {
|
|
|
110
110
|
* Read a content tree into the index a link resolves against.
|
|
111
111
|
*
|
|
112
112
|
* **The corpus comes from the content index, not from a walk of this module's
|
|
113
|
-
* own
|
|
113
|
+
* own**. Every pass used to answer "which files are the content?" for
|
|
114
114
|
* itself and throw the answer away; this one now reads
|
|
115
115
|
* {@link module:engine/content-index.indexRecordsFor}, which is the same
|
|
116
116
|
* derivation the published artifact and the compilers are driven from. So a
|
|
@@ -118,7 +118,7 @@ export function anchorsOf(body) {
|
|
|
118
118
|
* anchors it resolves against are the ones every other pass will emit — rather
|
|
119
119
|
* than a second derivation that agrees with them only by inspection. That was
|
|
120
120
|
* not hypothetical: this module carried its own anchor reader until the anchor
|
|
121
|
-
* half
|
|
121
|
+
* anchor half, and the two disagreed about which anchors existed.
|
|
122
122
|
*
|
|
123
123
|
* **The file is opened for its bytes and nothing else.** The index deliberately
|
|
124
124
|
* carries no note *body*, and a link lives in the body — so each note is read
|
|
@@ -138,7 +138,7 @@ export function anchorsOf(body) {
|
|
|
138
138
|
* @param {string} contentBase - Root of the content tree.
|
|
139
139
|
* @param {object} [opts]
|
|
140
140
|
* @param {object} [opts.config] - The resolved build configuration, whose
|
|
141
|
-
* fetched dependency indexes foreign addresses resolve through
|
|
141
|
+
* fetched dependency indexes foreign addresses resolve through, and
|
|
142
142
|
* whose `contentPackage` every local address is built from. Omitted, the
|
|
143
143
|
* ambient configuration is resolved and no cross-package address resolves.
|
|
144
144
|
* @param {readonly string[]} [opts.skipDirectories] - The walk's scope, passed
|
|
@@ -162,10 +162,10 @@ export function buildLinkIndex(
|
|
|
162
162
|
|
|
163
163
|
// The one package every note in this tree belongs to. Taken from the
|
|
164
164
|
// configuration this build resolved — never from a note (`package:` is
|
|
165
|
-
// retired, so there is no second source an address could disagree with
|
|
166
|
-
//
|
|
165
|
+
// retired, so there is no second source an address could disagree with)
|
|
166
|
+
// and never from the ambient one, which is a different configuration
|
|
167
167
|
// whenever a test injects one, `PACKAGE_BUILD_CONFIG` names one, or the
|
|
168
|
-
// command runs from a worktree
|
|
168
|
+
// command runs from a worktree.
|
|
169
169
|
const resolved = config ?? loadPackConfig();
|
|
170
170
|
const pkg = resolved.contentPackage;
|
|
171
171
|
|
|
@@ -197,12 +197,12 @@ export function buildLinkIndex(
|
|
|
197
197
|
notes.push(note);
|
|
198
198
|
|
|
199
199
|
// The anchors the index recorded, rather than a second reading of the
|
|
200
|
-
// same headings — the disagreement
|
|
200
|
+
// same headings — the disagreement the anchor half removes.
|
|
201
201
|
anchors.set(note, new Set((record.anchors ?? []).map((a) => a.slug)));
|
|
202
202
|
|
|
203
203
|
if (typeof fm.shortcode === "string" && fm.shortcode) {
|
|
204
204
|
// Canonical addresses only. Every written target expands to one
|
|
205
|
-
// before lookup
|
|
205
|
+
// before lookup, so there is nothing left for a short key to
|
|
206
206
|
// answer — and the short key was harmful: `type/shortcode` is
|
|
207
207
|
// system-blind, set with a plain `Map.set`, so two notes in one
|
|
208
208
|
// package sharing a `(type, shortcode)` across systems silently
|
|
@@ -245,7 +245,7 @@ export function buildLinkIndex(
|
|
|
245
245
|
/** The searchable universe a `dataview` table draws its rows from. */
|
|
246
246
|
const tableDocs = notes.map((n) => ({
|
|
247
247
|
// Package present for a `WHERE … package = "…"` clause, synthesised
|
|
248
|
-
// rather than authored — see {@link searchableFrontmatter}
|
|
248
|
+
// rather than authored — see {@link searchableFrontmatter}.
|
|
249
249
|
fm: searchableFrontmatter(n.fm, pkg),
|
|
250
250
|
path: n.rel,
|
|
251
251
|
tld: n.rel.split("/")[0],
|
|
@@ -259,14 +259,14 @@ export function buildLinkIndex(
|
|
|
259
259
|
* @returns {Array<{target: string, anchor: string, text: string,
|
|
260
260
|
* occurrence: number, labelled: boolean}>} `target` is `""` for a
|
|
261
261
|
* same-page `[[#anchor]]`; `labelled` says whether the link carries the
|
|
262
|
-
* `|` every link must have
|
|
262
|
+
* `|` every link must have.
|
|
263
263
|
*/
|
|
264
264
|
function linksOf(note) {
|
|
265
265
|
let body = note.body;
|
|
266
266
|
if (/^[ \t]*(?:`{3,}|~{3,})[ \t]*(?:dataview|sql)\b/im.test(body)) {
|
|
267
267
|
body = expandContentTables(body, {
|
|
268
268
|
// Unfiltered: every note in the tree is this package's, so
|
|
269
|
-
// there is no other package's note to exclude
|
|
269
|
+
// there is no other package's note to exclude.
|
|
270
270
|
docs: tableDocs,
|
|
271
271
|
linkable: (d) => Boolean(d.fm.shortcode),
|
|
272
272
|
source: note.file,
|
|
@@ -301,22 +301,11 @@ export function buildLinkIndex(
|
|
|
301
301
|
return out;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
/**
|
|
305
|
-
* The note an **address** names, or `undefined`.
|
|
306
|
-
*
|
|
307
|
-
* The qualifier is read with {@link readQualifier} rather than a second
|
|
308
|
-
* copy of the rule, so this cannot drift from what the builds do — the two
|
|
309
|
-
* separators, the first-hyphen split, and the optional leading package
|
|
310
|
-
* segment.
|
|
311
|
-
*
|
|
312
|
-
* @param {string} target - The link target, anchor already removed.
|
|
313
|
-
* @returns {object|undefined} The note it addresses.
|
|
314
|
-
*/
|
|
315
304
|
/**
|
|
316
305
|
* Every indexed entry an address names, matching only the segments it
|
|
317
306
|
* supplies.
|
|
318
307
|
*
|
|
319
|
-
* This is the whole
|
|
308
|
+
* This is the whole resolution rule in one place: a written
|
|
320
309
|
* address is a *partial* one, unsupplied segments are wildcards, and the
|
|
321
310
|
* caller requires exactly one hit. Nothing here decides an ambiguity — zero
|
|
322
311
|
* and many are different findings with different fixes, so the count is
|
|
@@ -351,10 +340,24 @@ export function buildLinkIndex(
|
|
|
351
340
|
return matchAddress([...byKey], q).map(([, v]) => v);
|
|
352
341
|
}
|
|
353
342
|
|
|
343
|
+
/**
|
|
344
|
+
* The note an **address** names, or `undefined`.
|
|
345
|
+
*
|
|
346
|
+
* The qualifier is read with {@link readQualifier} rather than a second
|
|
347
|
+
* copy of the rule, so this cannot drift from what the builds do — the two
|
|
348
|
+
* separators, the first-hyphen split, and the optional leading package
|
|
349
|
+
* segment.
|
|
350
|
+
*
|
|
351
|
+
* @param {string} target - The link target, anchor already removed.
|
|
352
|
+
* @param {string} [keyPath] - The dotted frontmatter key path the link sits
|
|
353
|
+
* under, which supplies the system an omitted segment defaults to — see
|
|
354
|
+
* {@link blockSystem}. Body prose has none.
|
|
355
|
+
* @returns {object|undefined} The note it addresses.
|
|
356
|
+
*/
|
|
354
357
|
function resolveAddress(target, keyPath) {
|
|
355
358
|
const qualified = readQualifier(target, types, packages);
|
|
356
359
|
if (!qualified || qualified.reason) return undefined;
|
|
357
|
-
// Every omitted segment defaults from where the link is written
|
|
360
|
+
// Every omitted segment defaults from where the link is written,
|
|
358
361
|
// so the target expands to exactly one canonical address and this is a
|
|
359
362
|
// plain lookup. There is no candidate set, and therefore no single-hit
|
|
360
363
|
// rule and no ambiguity to report.
|
|
@@ -370,21 +373,24 @@ export function buildLinkIndex(
|
|
|
370
373
|
* Every foreign manifest entry an address names, in package order.
|
|
371
374
|
*
|
|
372
375
|
* A written target is a **partial** address, so this matches on the
|
|
373
|
-
* segments it supplies and wildcards the rest
|
|
376
|
+
* segments it supplies and wildcards the rest. A target naming a
|
|
374
377
|
* package necessarily names its system too — omission runs left to right —
|
|
375
378
|
* so the fully qualified form matches at most one entry; a shorter one
|
|
376
379
|
* names no package, and resolves against any foreign package that
|
|
377
380
|
* publishes it. Either way only exactly one hit resolves. Two claimants make it ambiguous, which is a different finding
|
|
378
381
|
* from resolving nowhere and has a different fix, so the count is returned
|
|
379
|
-
* rather than collapsed here
|
|
382
|
+
* rather than collapsed here.
|
|
380
383
|
*
|
|
381
384
|
* @param {string} target - The link target.
|
|
385
|
+
* @param {string} [keyPath] - The dotted frontmatter key path the link sits
|
|
386
|
+
* under, which supplies the system an omitted segment defaults to — see
|
|
387
|
+
* {@link blockSystem}. Body prose has none.
|
|
382
388
|
* @returns {object[]} The foreign entries, each carrying its `package`.
|
|
383
389
|
*/
|
|
384
390
|
function foreignHits(target, keyPath) {
|
|
385
391
|
const q = readQualifier(target, types, packages);
|
|
386
392
|
if (!q || q.reason) return [];
|
|
387
|
-
// An omitted package means *this* package
|
|
393
|
+
// An omitted package means *this* package, so a short form
|
|
388
394
|
// addresses nothing foreign and never reaches a dependency's index.
|
|
389
395
|
// Reaching another package is the fully qualified form's job, and
|
|
390
396
|
// saying so is the whole point: a link that resolved into `sohl` only
|
|
@@ -411,6 +417,32 @@ export function buildLinkIndex(
|
|
|
411
417
|
return hits.length === 1 ? hits[0] : null;
|
|
412
418
|
}
|
|
413
419
|
|
|
420
|
+
/**
|
|
421
|
+
* The note or foreign entry a **frontmatter reference** names, or null.
|
|
422
|
+
*
|
|
423
|
+
* A `ref:` field holds a shortcode, not an address. The system persists it
|
|
424
|
+
* verbatim and resolves it at runtime against the items embedded on one
|
|
425
|
+
* actor, where packages do not exist: an actor assembled from several
|
|
426
|
+
* packages carries their items side by side. So a reference resolves when
|
|
427
|
+
* *any* reachable package declares the `(type, shortcode)` pair — package
|
|
428
|
+
* and system wildcarded, local notes first, then the fetched indexes.
|
|
429
|
+
*
|
|
430
|
+
* Distinct from {@link resolveAddress}, which defaults an omitted package
|
|
431
|
+
* to this one. That is the rule for a link, whose target is a document to
|
|
432
|
+
* point at; a reference names an item to stand beside.
|
|
433
|
+
*
|
|
434
|
+
* @param {string} target - The reference as `type-shortcode`.
|
|
435
|
+
* @returns {object|null} The note or foreign entry declaring it.
|
|
436
|
+
*/
|
|
437
|
+
function referenceHit(target) {
|
|
438
|
+
const q = readQualifier(target, types, packages);
|
|
439
|
+
if (!q || q.reason) return null;
|
|
440
|
+
const local = matchAddress([...byKey], q);
|
|
441
|
+
if (local.length) return local[0][1];
|
|
442
|
+
const abroad = matchAddress([...foreign.index], q);
|
|
443
|
+
return abroad.length ? abroad[0][1] : null;
|
|
444
|
+
}
|
|
445
|
+
|
|
414
446
|
return {
|
|
415
447
|
notes,
|
|
416
448
|
frontmatterLinks,
|
|
@@ -428,12 +460,13 @@ export function buildLinkIndex(
|
|
|
428
460
|
/**
|
|
429
461
|
* Resolve a link target the way both builds do, or `undefined`. Every
|
|
430
462
|
* link is an address, so this is {@link resolveAddress} under the name
|
|
431
|
-
* the walkers use
|
|
463
|
+
* the walkers use.
|
|
432
464
|
*/
|
|
433
465
|
resolve: resolveAddress,
|
|
434
466
|
resolveAddress,
|
|
435
467
|
manifestHit,
|
|
436
468
|
foreignHits,
|
|
469
|
+
referenceHit,
|
|
437
470
|
/** Whether a target reads as a qualified address at all. */
|
|
438
471
|
isAddress: (target) => Boolean(readQualifier(target, types, packages)),
|
|
439
472
|
};
|
|
@@ -453,7 +486,7 @@ export function buildLinkIndex(
|
|
|
453
486
|
const SITE_HOST = /^(?:[a-z0-9-]+\.)*heroiclands\.org$/i;
|
|
454
487
|
|
|
455
488
|
/**
|
|
456
|
-
* Every package landing this build can name, as `package` → base
|
|
489
|
+
* Every package landing this build can name, as `package` → base.
|
|
457
490
|
*
|
|
458
491
|
* **A landing needs no manifest, and that is what makes it work.** The link
|
|
459
492
|
* manifest indexes content notes, and a homepage is deliberately not one — it
|
|
@@ -578,13 +611,13 @@ function readAddress(url, packages) {
|
|
|
578
611
|
* **What is checkable, stated plainly.** Only an address into this site is, and
|
|
579
612
|
* only against facts this build already holds:
|
|
580
613
|
*
|
|
581
|
-
* - A **retired content type** in the path. The engine knows
|
|
614
|
+
* - A **retired content type** in the path. The engine knows the retired names
|
|
582
615
|
* and what replaced it, so this is a fact rather than a guess — and it is
|
|
583
616
|
* exactly the SoHL defect.
|
|
584
617
|
* - A **hardcoded absolute URL** into this package's own prefix, or into one a
|
|
585
618
|
* a fetched index names. Every one of them has a better form to write, which
|
|
586
619
|
* is why every one is reported — including a bare `/<package>/`, which names
|
|
587
|
-
* another package's landing
|
|
620
|
+
* another package's landing.
|
|
588
621
|
*
|
|
589
622
|
* That last case was exempt until the better form was identified, on the
|
|
590
623
|
* reasoning that a landing is in no link manifest so nothing could resolve it.
|
|
@@ -658,7 +691,7 @@ export function auditHomepageLinks(index) {
|
|
|
658
691
|
// Landings first, and by the roster rather than by the manifest
|
|
659
692
|
// package set: a landing is addressable in a repository that
|
|
660
693
|
// has fetched no index at all, which is the case the fence creates
|
|
661
|
-
// and the case this rule exists for
|
|
694
|
+
// and the case this rule exists for.
|
|
662
695
|
const landing = landingTarget(url, bases);
|
|
663
696
|
if (landing) {
|
|
664
697
|
report(
|
|
@@ -749,7 +782,7 @@ export function auditHomepageLinks(index) {
|
|
|
749
782
|
*
|
|
750
783
|
* **How the link is *written* is a separate finding from where it points**, and
|
|
751
784
|
* the two are kept apart because the corrections differ. An unlabelled link
|
|
752
|
-
*
|
|
785
|
+
* has to become `[[type-shortcode|Text]]`; a labelled one whose target
|
|
753
786
|
* resolves nowhere has a shortcode to fix. Reporting a bare `[[Name]]` as a
|
|
754
787
|
* dead address would send an author hunting for a note that was never named.
|
|
755
788
|
*
|
|
@@ -761,7 +794,7 @@ export function auditHomepageLinks(index) {
|
|
|
761
794
|
* carries a `reason` from {@link LINK_FINDING_REASONS} —
|
|
762
795
|
* `"not-an-address"`, `"unknown-type"`, `"ambiguous"` (with the claiming
|
|
763
796
|
* `packages`), or `"unresolved"` — and every one of them is an **error**:
|
|
764
|
-
* the three resolvers agree on severity for every class
|
|
797
|
+
* the three resolvers agree on severity for every class.
|
|
765
798
|
*/
|
|
766
799
|
export function auditLinks(index) {
|
|
767
800
|
const { notes, anchors, linksOf, resolve, manifestHit, isAddress } = index;
|
|
@@ -792,7 +825,7 @@ export function auditLinks(index) {
|
|
|
792
825
|
for (const note of notes) {
|
|
793
826
|
for (const { target, anchor, text, occurrence, labelled } of linksOf(note)) {
|
|
794
827
|
// The label is required whatever the link part is, an anchor
|
|
795
|
-
// included — so this is tested before the same-page form
|
|
828
|
+
// included — so this is tested before the same-page form.
|
|
796
829
|
if (!labelled) {
|
|
797
830
|
unlabelledLinks.push({
|
|
798
831
|
note,
|
|
@@ -824,7 +857,7 @@ export function auditLinks(index) {
|
|
|
824
857
|
// Two packages publish the short address, so it names neither.
|
|
825
858
|
// Reported as its own class: "no document has that identity" is
|
|
826
859
|
// false here — two do — and the fix is the qualified form
|
|
827
|
-
// rather than a corrected shortcode
|
|
860
|
+
// rather than a corrected shortcode.
|
|
828
861
|
deadAddresses.push({
|
|
829
862
|
...at,
|
|
830
863
|
reason: "ambiguous",
|