@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
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* questions asked of the *list* rather than of any one map.
|
|
17
17
|
*
|
|
18
18
|
* One frozen list, and the two lookups that need to choose among its members
|
|
19
|
-
* before a map can be consulted at all. It
|
|
19
|
+
* before a map can be consulted at all. It sits here rather than in
|
|
20
|
+
* `note-claims.mjs`,
|
|
20
21
|
* which is where the *questions* asked of it live — but that module imports
|
|
21
22
|
* half the engine, so anything needing the bare list had to take all of it, and
|
|
22
23
|
* `helpers.mjs` could not take it at all: `note-claims.mjs` imports
|
|
@@ -42,7 +43,7 @@ import { HM3_DOCUMENT_SUBTYPES } from "../hm3/document-subtypes.mjs";
|
|
|
42
43
|
/**
|
|
43
44
|
* The note-type → document-subtype maps this toolchain ships.
|
|
44
45
|
*
|
|
45
|
-
* Two, since `hm3/` landed
|
|
46
|
+
* Two, since `hm3/` landed — and it joined this list rather than the
|
|
46
47
|
* claim table growing a second copy of the same fact, which is what the list
|
|
47
48
|
* was for.
|
|
48
49
|
*
|
|
@@ -72,7 +73,7 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS = Object.freeze([
|
|
|
72
73
|
* {@link KNOWN_DOCUMENT_SUBTYPE_MAPS} exists rather than a hand-kept table.
|
|
73
74
|
*
|
|
74
75
|
* It exists because an actor note publishes documentation like every other
|
|
75
|
-
* system-bearing note
|
|
76
|
+
* system-bearing note. `docEntryTypes` was `itemTypes` plus `macro` and
|
|
76
77
|
* the map types, which left a being as the one system-bearing note with no
|
|
77
78
|
* `none` address — nothing a prose link could land on, since its only address
|
|
78
79
|
* named the Actor. Composing that set needs to know which types are actors, and
|
|
@@ -111,7 +112,7 @@ export function subtypeMapFor(system) {
|
|
|
111
112
|
* translation the *schema* check needs, and the reason it needs one.
|
|
112
113
|
*
|
|
113
114
|
* A schema artifact is keyed by document subtype; a field declaration is keyed
|
|
114
|
-
* by note type. Those
|
|
115
|
+
* by note type. Those would be the same string but for three renames, and
|
|
115
116
|
* a check that went on joining them by name simply stopped reporting on
|
|
116
117
|
* `armorgear` — a warning that vanishes is worse than one that fires, because
|
|
117
118
|
* nothing says it went.
|
package/engine/system-block.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* **The per-system frontmatter block** — how one note feeds more than one game
|
|
16
|
-
* system
|
|
16
|
+
* system.
|
|
17
17
|
*
|
|
18
18
|
* A note is system-agnostic. The only system-specific things it carries are the
|
|
19
19
|
* properties *named after a system*, and within one:
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* | property | maps to |
|
|
22
22
|
* | --- | --- |
|
|
23
23
|
* | `<system>.system` | `document.system` — the DataModel schema, verbatim paths |
|
|
24
|
-
* | `<system>.type` | `document.type` — the subtype
|
|
24
|
+
* | `<system>.type` | `document.type` — the subtype discriminator |
|
|
25
25
|
* | `<system>.img` | `document.img` |
|
|
26
26
|
* | `<system>.items` | `document.items` — actors only |
|
|
27
27
|
* | `<system>.effects` | `document.effects` |
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*
|
|
48
48
|
* 1. `S.system.<to>` — authored directly, wins outright;
|
|
49
49
|
* 2. `S.<legacyKey>` — the legacy in-block position the corpus still writes,
|
|
50
|
-
* kept until
|
|
50
|
+
* kept until the corpus moves it;
|
|
51
51
|
* 3. the shared top-level property the field **declares** as its source, which
|
|
52
52
|
* may be a dotted path (`data.portrait`) rather than a sibling key;
|
|
53
53
|
* 3b. for a `data.` source, the bare top-level key that container gathered it
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
*
|
|
57
57
|
* ## Steps 2 and 3 are two declarations, because they are two positions
|
|
58
58
|
*
|
|
59
|
-
* They
|
|
60
|
-
* shared source and the in-block key were the same word. `data:`
|
|
59
|
+
* They are not one: keying both on `name` holds only while the
|
|
60
|
+
* shared source and the in-block key were the same word. `data:` ended
|
|
61
61
|
* that — a shared source is now a path *into* a container, so `data.species`
|
|
62
62
|
* and `species` are two spellings of two different places, and no single value
|
|
63
63
|
* of `name` reached both. `name: "species"` read `hm3.species` and could not
|
|
64
64
|
* see `data.species`; `name: "data.species"` read the shared source and could
|
|
65
65
|
* not see `hm3.species`. Each yielded the field's **default** wherever only the
|
|
66
66
|
* other position was authored — silently, since the field compiles and the
|
|
67
|
-
* document is emitted with the value simply gone
|
|
67
|
+
* document is emitted with the value simply gone.
|
|
68
68
|
*
|
|
69
69
|
* That made every move into `data:` a flag day. Each of this package's other
|
|
70
70
|
* retirements — `package:`, `image`, `archetype`, `relation` — works because
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
* that declares one is mid-sweep by construction, which is what
|
|
78
78
|
* {@link module:engine/field-spec.readsLegacyKey} reports on.
|
|
79
79
|
*
|
|
80
|
-
* ## Step 3 has a retiring position too, and it is derived
|
|
80
|
+
* ## Step 3 has a retiring position too, and it is derived
|
|
81
81
|
*
|
|
82
82
|
* `legacyKey` retires the *in-block* position, and for a while that looked like
|
|
83
83
|
* the whole of what `data:` left behind. It is not. The facts `data:` holds
|
|
84
|
-
* were not invented by it —
|
|
84
|
+
* were not invented by it — the sweep **gathered** them out of the note's open top
|
|
85
85
|
* level, where `portrait:` sat beside `img:` and `shortcode:` — so a field that
|
|
86
86
|
* declares `data.portrait` has two shared spellings to read, not one, and
|
|
87
87
|
* reading only the current one is the same silent miss `legacyKey` exists to
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
* It was not a harmless coincidence either, because step 3 answers **without**
|
|
113
113
|
* applying `field.default` — only step 2 does — so an authored `title: null`
|
|
114
114
|
* reached the field's coercion unguarded and shipped as the literal string
|
|
115
|
-
* `"null"` in fifteen documents
|
|
115
|
+
* `"null"` in fifteen documents.
|
|
116
116
|
*
|
|
117
117
|
* So a field may declare `topLevelMeans`: what the top-level key of that name
|
|
118
118
|
* means *instead*. Declaring it removes the whole shared level — step 3 and the
|
|
@@ -132,9 +132,8 @@
|
|
|
132
132
|
*
|
|
133
133
|
* A block is addressed by name, and the name arrives from the caller — a
|
|
134
134
|
* system's own document-subtype map declares it (`DocumentSubtypeMap.block`).
|
|
135
|
-
* That is the `engine/` ÷ `sohl/` line this package draws everywhere else
|
|
136
|
-
*
|
|
137
|
-
* half.
|
|
135
|
+
* That is the `engine/` ÷ `sohl/` line this package draws everywhere else:
|
|
136
|
+
* note-format knowledge here, game-system knowledge in the system's own half.
|
|
138
137
|
*
|
|
139
138
|
* @module
|
|
140
139
|
*/
|
|
@@ -393,7 +392,7 @@ const DATA_PREFIX = "data.";
|
|
|
393
392
|
/**
|
|
394
393
|
* The bare top-level key a `data:`-sourced field is being swept off — step 3b.
|
|
395
394
|
*
|
|
396
|
-
* `data:`
|
|
395
|
+
* `data:` did not invent the facts it holds; it *gathered* them, out of
|
|
397
396
|
* the note's open top level where each was a sibling of `img` and `shortcode`.
|
|
398
397
|
* So the retiring spelling of `data.portrait` is not a second declaration
|
|
399
398
|
* anyone has to write — it is `portrait`, mechanically, and the same holds for
|
|
@@ -466,8 +465,8 @@ export function resolveFieldValue(field, fm, { block = "sohl" } = {}) {
|
|
|
466
465
|
|
|
467
466
|
// 2. The legacy in-block position, keyed on `legacyKey` — the shared
|
|
468
467
|
// source is a path into `data:` and the in-block key is a bare word, so
|
|
469
|
-
// the two are declared separately
|
|
470
|
-
// writes here today, and will until
|
|
468
|
+
// the two are declared separately. Every note in every tree
|
|
469
|
+
// writes here today, and will until the corpus moves them; dropping it would
|
|
471
470
|
// be a corpus migration disguised as a mechanism change.
|
|
472
471
|
const declared = systemBlock(fm, block);
|
|
473
472
|
const legacyKey = legacyKeyOf(field);
|
|
@@ -482,14 +481,14 @@ export function resolveFieldValue(field, fm, { block = "sohl" } = {}) {
|
|
|
482
481
|
|
|
483
482
|
// 3. The shared property this field declares as its source — unless the
|
|
484
483
|
// field declares that the top-level key of that name means something
|
|
485
|
-
// else, in which case there is no shared position to read
|
|
484
|
+
// else, in which case there is no shared position to read.
|
|
486
485
|
if (field.topLevelMeans === undefined) {
|
|
487
486
|
const shared = getFrontmatter(fm, field.name, undefined);
|
|
488
487
|
if (shared !== undefined) return { value: shared, from: "shared" };
|
|
489
488
|
|
|
490
489
|
// 3b. The bare top-level key the `data:` source was gathered off — the
|
|
491
490
|
// retiring *shared* position, exactly as `legacyKey` is the
|
|
492
|
-
// retiring *in-block* one
|
|
491
|
+
// retiring *in-block* one. Without it a field declaring
|
|
493
492
|
// `data.portrait` cannot see the `portrait:` every tree still
|
|
494
493
|
// writes, and the miss arrives at the caller's `?? default` as an
|
|
495
494
|
// ordinary absence: 646 `sohl-thalorna` beings compiled the generic
|
|
@@ -556,7 +555,7 @@ export function systemDataPaths(data, prefix = "") {
|
|
|
556
555
|
*
|
|
557
556
|
* It stayed invisible while those maps were authored *outside* `<system>.system`
|
|
558
557
|
* and so were never walked. The moment the corpus moves them to the destination
|
|
559
|
-
*
|
|
558
|
+
* every one of them lights up — 62 findings on `sohl-thalorna` alone,
|
|
560
559
|
* none of them a defect.
|
|
561
560
|
*
|
|
562
561
|
* So descent is conditional on the schema declaring something *beneath* the
|
|
@@ -621,7 +620,7 @@ function declaresChildren(declared, path) {
|
|
|
621
620
|
* @param {object} options - Options.
|
|
622
621
|
* @param {Iterable<string>} options.known - The keys this system declares on
|
|
623
622
|
* top of the shared vocabulary: its generators, its toolchain keys, and —
|
|
624
|
-
* until
|
|
623
|
+
* until the corpus moves them — the field names its notes still author in the
|
|
625
624
|
* block.
|
|
626
625
|
* @returns {string[]} The unrecognized keys, in authored order.
|
|
627
626
|
*/
|
package/engine/systems.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* The closed registry of system ids, and the `none` that stands for no system
|
|
16
|
-
* at all
|
|
16
|
+
* at all.
|
|
17
17
|
*
|
|
18
18
|
* A canonical address carries the system in a fixed position —
|
|
19
19
|
* `harnadventures-none-being-grod` — so "which systems exist" is a question the
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
// The one charset, read rather than restated — a system id is an address
|
|
49
49
|
// segment like any other, and a second spelling of the pattern is how the
|
|
50
|
-
// disagreements
|
|
50
|
+
// disagreements between them arise.
|
|
51
51
|
import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./address-charset.mjs";
|
|
52
52
|
|
|
53
53
|
/**
|
package/engine/web-wikilinks.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* `[[type-shortcode#slug|Text]]` → `[Text](/section/slug/#slug)`
|
|
23
23
|
* `[[#slug|Text]]` → `[Text](#slug)`
|
|
24
24
|
*
|
|
25
|
-
* **Every link is an address and carries a label
|
|
25
|
+
* **Every link is an address and carries a label**. One written without
|
|
26
26
|
* a label addresses nothing and is reported — see
|
|
27
27
|
* {@link unlabelledLinkMessage}, which states the rule for this build and the
|
|
28
28
|
* pack build together.
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
* Lives here rather than in a consumer so every package resolves a link the
|
|
35
35
|
* same way. `sohl-thalorna` carried a forked copy of this as
|
|
36
36
|
* `utils/site-wikilinks.mjs`, already 3 KB adrift from the original, which is
|
|
37
|
-
* exactly the drift one rule with two implementations produces
|
|
37
|
+
* exactly the drift one rule with two implementations produces.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
// How an address *parses* is the pack build's own rule, so the two builds
|
|
41
41
|
// cannot drift apart on it: they disagreed once over the unlabelled hyphen
|
|
42
42
|
// form, which the packs showed as a raw shortcode and the knowledgebase as a
|
|
43
|
-
// name
|
|
43
|
+
// name.
|
|
44
44
|
import { readQualifier } from "./wikilinks.mjs";
|
|
45
45
|
import { replaceOutsideCode } from "./code-fences.mjs";
|
|
46
46
|
// The canonical `package-system-type-shortcode` key, so a package-qualified
|
|
47
47
|
// address is matched the way a vendored manifest publishes it — by the
|
|
48
|
-
// segments the target supplies, with the system wildcarded unless stated
|
|
48
|
+
// segments the target supplies, with the system wildcarded unless stated.
|
|
49
49
|
import { canonicalKey, expandAddress, readCanonicalKey } from "./content-address.mjs";
|
|
50
50
|
import { NO_SYSTEM } from "./systems.mjs";
|
|
51
51
|
// The one rule about a link's shape both builds share: it carries a label, and
|
|
52
|
-
// {@link unlabelledLinkMessage} is the one place that says so
|
|
52
|
+
// {@link unlabelledLinkMessage} is the one place that says so.
|
|
53
53
|
import { unlabelledLinkMessage } from "./wikilink-syntax.mjs";
|
|
54
54
|
// One slug rule for the whole build — see `./content-slug.mjs`. This module
|
|
55
55
|
// carried a copy that dropped non-ASCII letters rather than transliterating
|
|
@@ -58,17 +58,16 @@ import { slugify } from "./content-slug.mjs";
|
|
|
58
58
|
|
|
59
59
|
// Re-exported so a site build keeps one import path for the whole of link
|
|
60
60
|
// resolution: the same rule that names a page also names an anchor within it.
|
|
61
|
+
/** KB heading/anchor slug: lowercase, non-alphanumerics to single hyphens. */
|
|
61
62
|
export { slugify };
|
|
62
63
|
import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-syntax.mjs";
|
|
63
64
|
|
|
64
|
-
/** KB heading/anchor slug: lowercase, non-alphanumerics to single hyphens. */
|
|
65
|
-
|
|
66
65
|
/**
|
|
67
66
|
* The index key a **piped** target resolves to, or `null` when it does not
|
|
68
67
|
* parse as an address at all.
|
|
69
68
|
*
|
|
70
69
|
* The KB index is keyed by the canonical `type/shortcode`, so a target written
|
|
71
|
-
* in the hyphen separator — which is what the content tree authors
|
|
70
|
+
* in the hyphen separator — which is what the content tree authors —
|
|
72
71
|
* has to be rewritten to it before lookup. The target is read by the pack
|
|
73
72
|
* build's own {@link readQualifier}, so recognising an address and resolving
|
|
74
73
|
* one can never disagree: the two separators and the optional leading package
|
|
@@ -77,18 +76,22 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
|
|
|
77
76
|
* It takes the **parsed** qualifier rather than the raw target because the
|
|
78
77
|
* caller needs the parse for a second purpose: `unknown-type` and
|
|
79
78
|
* `not-an-address` are different findings with different fixes, and only the
|
|
80
|
-
* `reason` tells them apart
|
|
79
|
+
* `reason` tells them apart. Reading the target twice would let the two
|
|
81
80
|
* readings drift.
|
|
82
81
|
*
|
|
83
82
|
* The build indexes an item note under both `skill/climb` and `docskill/climb`,
|
|
84
83
|
* and `contentTypes` carries both qualifiers, so either form finds the page.
|
|
85
84
|
*
|
|
85
|
+
* @param {Map<string, object>} index - The page index to look in.
|
|
86
86
|
* @param {object|null} read - From {@link readQualifier}.
|
|
87
|
-
* @
|
|
87
|
+
* @param {string} contentPackage - The citing package, which an omitted package
|
|
88
|
+
* segment defaults from.
|
|
89
|
+
* @returns {object|undefined} The indexed page, or `undefined` when the target
|
|
90
|
+
* is not an address or nothing publishes it.
|
|
88
91
|
*/
|
|
89
92
|
function lookupRead(index, read, contentPackage) {
|
|
90
93
|
if (!read || read.reason) return undefined;
|
|
91
|
-
// Every omitted segment defaults from where the link is written
|
|
94
|
+
// Every omitted segment defaults from where the link is written —
|
|
92
95
|
// package from the citing package, system from the block, which on a page
|
|
93
96
|
// body is `none` — so the target expands to exactly one canonical address
|
|
94
97
|
// and this is a plain lookup. No filter, no single-hit rule, and no
|
|
@@ -104,7 +107,7 @@ function lookupRead(index, read, contentPackage) {
|
|
|
104
107
|
// A local target may also be keyed by its short form. On the KB that is not
|
|
105
108
|
// a second answer: an item note renders as **one page** which is its own
|
|
106
109
|
// documentation, so `skill/climb`, `docskill/climb` and both canonical
|
|
107
|
-
// addresses are all the same value
|
|
110
|
+
// addresses are all the same value — the fallback cannot pick
|
|
108
111
|
// differently, only earlier. It stays because an index built before the
|
|
109
112
|
// canonical documentation key was added still carries the short one, and
|
|
110
113
|
// because a page collision here is caught by the site index's own
|
|
@@ -127,10 +130,10 @@ function lookupRead(index, read, contentPackage) {
|
|
|
127
130
|
* This is deliberately identical to the pack compiler's own `unresolvedLink`,
|
|
128
131
|
* down to the class name and the `title` wording. One authored link renders on
|
|
129
132
|
* two surfaces, and the two builds have drifted before over exactly this kind
|
|
130
|
-
* of detail
|
|
133
|
+
* of detail — matching markup is what keeps a reader's cue the same in
|
|
131
134
|
* a journal and on the page. Duplicated rather than imported only because the
|
|
132
135
|
* function is not exported from `@heroiclands/package-build`; hoisting it there
|
|
133
|
-
* is
|
|
136
|
+
* is.
|
|
134
137
|
*
|
|
135
138
|
* The knowledgebase renders with `unsafe = true` (`kb/hugo.toml`), so raw HTML
|
|
136
139
|
* in generated markdown reaches the page. That makes escaping obligatory: this
|
|
@@ -154,7 +157,7 @@ function unresolvedLink(text, target) {
|
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
/**
|
|
157
|
-
* How a link to a **draft** note renders
|
|
160
|
+
* How a link to a **draft** note renders.
|
|
158
161
|
*
|
|
159
162
|
* A note tagged `draft` exists so a link into it is not dead, and nothing more.
|
|
160
163
|
* Unmarked, a reader follows a promising link into an empty page and an author
|
|
@@ -173,8 +176,8 @@ function unresolvedLink(text, target) {
|
|
|
173
176
|
* **Byte-identical with the pack build's copy** in `wikilinks.mjs`, down to the
|
|
174
177
|
* class name and the `title` wording — one authored link renders on two
|
|
175
178
|
* surfaces, and the two builds have drifted before over exactly this kind of
|
|
176
|
-
* detail
|
|
177
|
-
* {@link unresolvedLink} is; hoisting both is
|
|
179
|
+
* detail. Duplicated rather than imported for the same reason
|
|
180
|
+
* {@link unresolvedLink} is; hoisting both is.
|
|
178
181
|
*
|
|
179
182
|
* The argument is already-built markup and is deliberately not escaped; the
|
|
180
183
|
* *authored* text inside it was escaped, or made into a link, by the caller.
|
|
@@ -198,7 +201,7 @@ function draftLink(inner) {
|
|
|
198
201
|
const FRONTMATTER_WIKILINK = new RegExp(WIKILINK.source, "g");
|
|
199
202
|
|
|
200
203
|
/**
|
|
201
|
-
* Every wikilink authored inside a frontmatter value
|
|
204
|
+
* Every wikilink authored inside a frontmatter value.
|
|
202
205
|
*
|
|
203
206
|
* Wikilinks are resolved in a note's **body** — by {@link resolveWebWikilinks}
|
|
204
207
|
* here, and by the pack compilers' `convertWikilinks` for Foundry. Frontmatter
|
|
@@ -267,19 +270,19 @@ function isPlainMap(value) {
|
|
|
267
270
|
* `section/slug`), then in the vendored `ctx.foreign` manifests. A target is a
|
|
268
271
|
* *partial* address: an omitted package means this package, an omitted system
|
|
269
272
|
* is a wildcard, and exactly one match resolves — two are `ambiguous`. A link written
|
|
270
|
-
* without a label addresses nothing at all and is reported as such
|
|
273
|
+
* without a label addresses nothing at all and is reported as such —
|
|
271
274
|
* there is no second namespace left for it to name.
|
|
272
275
|
*
|
|
273
276
|
* Only a slash-qualified target reaches the raw key, which is what keeps
|
|
274
277
|
* `section/slug` addressable without a page's own slug answering for it.
|
|
275
278
|
*
|
|
276
|
-
* **Every target that resolves nowhere fails the build
|
|
279
|
+
* **Every target that resolves nowhere fails the build**, and is
|
|
277
280
|
* classified into the vocabulary all three resolvers share — `unlabelled`,
|
|
278
281
|
* `not-an-address`, `unknown-type`, `ambiguous`, `unresolved`. Failures are
|
|
279
282
|
* collected in `ctx.errors`, each carrying the authored `link` and its
|
|
280
283
|
* `occurrence` so a caller can report the line and column it sits on.
|
|
281
284
|
*
|
|
282
|
-
* There
|
|
285
|
+
* There is deliberately no exception letting a hyphen-form address through while
|
|
283
286
|
* any linkable package had no vendored manifest, since a real cross-package
|
|
284
287
|
* reference and a typo look identical from here. The pack compilers and the
|
|
285
288
|
* link checker never made that allowance, so its only surviving effect was to
|
|
@@ -287,13 +290,13 @@ function isPlainMap(value) {
|
|
|
287
290
|
* instead.
|
|
288
291
|
*
|
|
289
292
|
* A target that resolves nowhere still renders through {@link unresolvedLink}
|
|
290
|
-
* rather than as bare prose
|
|
293
|
+
* rather than as bare prose: the author's text is kept, marked so a
|
|
291
294
|
* reader can see a link was intended. The marking and the failure are separate
|
|
292
295
|
* jobs and always were — the mark is for whoever reads the page a *previous*
|
|
293
296
|
* build emitted, the failure is for the author of this one.
|
|
294
297
|
*
|
|
295
298
|
* A target that **resolved** to an entry with no page is not this case and is
|
|
296
|
-
* not marked: a pack-only package
|
|
299
|
+
* not marked: a pack-only package publishes Foundry addresses and no
|
|
297
300
|
* web pages, so the author wrote a real address and there is simply nothing to
|
|
298
301
|
* link to.
|
|
299
302
|
*
|
|
@@ -302,7 +305,7 @@ function isPlainMap(value) {
|
|
|
302
305
|
* foreign, type, errors, src, file }`.
|
|
303
306
|
* `packages` is every package an address may name, without which the leading
|
|
304
307
|
* package segment of a canonical address reads as an unknown type; `foreign`
|
|
305
|
-
* is the cross-package manifest index
|
|
308
|
+
* is the cross-package manifest index. `src` is the page's display
|
|
306
309
|
* path and `file` the source file a diagnostic should name — absent, `src`
|
|
307
310
|
* stands in.
|
|
308
311
|
* @returns {string} The body with wikilinks rewritten.
|
|
@@ -311,7 +314,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
311
314
|
// How many times each authored link has been seen, so two identical links
|
|
312
315
|
// on one page are located at their own positions in the source file — the
|
|
313
316
|
// same counting the checker does, and what turns a finding into a
|
|
314
|
-
// `file:line:column:` diagnostic rather than a note-wide one
|
|
317
|
+
// `file:line:column:` diagnostic rather than a note-wide one.
|
|
315
318
|
const seen = new Map();
|
|
316
319
|
/**
|
|
317
320
|
* Records a finding, and returns the marked-up link it renders as.
|
|
@@ -338,16 +341,16 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
338
341
|
};
|
|
339
342
|
|
|
340
343
|
// Code is verbatim: a `[[…]]` inside a code fence, an indented block or an
|
|
341
|
-
// inline span is source text, not a link
|
|
344
|
+
// inline span is source text, not a link.
|
|
342
345
|
return replaceOutsideCode(body, WIKILINK, (all, rawInner) => {
|
|
343
346
|
const parsed = parseWikilink(rawInner);
|
|
344
347
|
const { target, anchor, display } = parsed;
|
|
345
348
|
// An empty label is not a label: `[[x|]]` addresses the target and
|
|
346
|
-
// shows its name, so `""` falls through to the same place `null` does
|
|
347
|
-
//
|
|
349
|
+
// shows its name, so `""` falls through to the same place `null` does.
|
|
350
|
+
// One reading, from {@link authoredLabel}.
|
|
348
351
|
const label = authoredLabel({ display });
|
|
349
352
|
|
|
350
|
-
// **Every link carries a label
|
|
353
|
+
// **Every link carries a label**. Without one there is nothing
|
|
351
354
|
// to resolve against — the alias namespace a bare `[[Text]]` named is
|
|
352
355
|
// retired — and nothing to show either, a shortcode being an address
|
|
353
356
|
// rather than prose. Reported before the same-page form, because the
|
|
@@ -362,7 +365,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
362
365
|
return `[${label ?? anchor}](#${slugify(anchor)})`;
|
|
363
366
|
}
|
|
364
367
|
|
|
365
|
-
// The canonical separator
|
|
368
|
+
// The canonical separator has to be resolved, not merely
|
|
366
369
|
// recognised. `null` here means the target is not an address at all,
|
|
367
370
|
// which is a defect: there is no other namespace to try.
|
|
368
371
|
const read = readQualifier(target, ctx.contentTypes ?? new Set(), ctx.packages);
|
|
@@ -374,10 +377,10 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
374
377
|
// a page's bare slug cannot answer for an address.
|
|
375
378
|
(rawKey.includes("/") ? ctx.index.get(rawKey) : undefined) ??
|
|
376
379
|
// A manifest entry carries the same `{ url, name }` shape as a
|
|
377
|
-
// local one
|
|
380
|
+
// local one, so a cross-package hit needs no special case
|
|
378
381
|
// below. Local wins: a live build is authoritative and a vendored
|
|
379
382
|
// manifest can only be staler.
|
|
380
|
-
// A short form names *this* package
|
|
383
|
+
// A short form names *this* package, so it never reaches a
|
|
381
384
|
// vendored manifest; only a fully qualified address does.
|
|
382
385
|
(ctx.foreign && read?.package ?
|
|
383
386
|
lookupRead(ctx.foreign, read, ctx.contentPackage)
|
|
@@ -387,8 +390,8 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
387
390
|
// shortcode is not display text), so the document's **current**
|
|
388
391
|
// name stands in and a rename shows at every citation.
|
|
389
392
|
const text = label ?? hit.name;
|
|
390
|
-
// A pack-only package publishes Foundry addresses and no pages
|
|
391
|
-
//
|
|
393
|
+
// A pack-only package publishes Foundry addresses and no pages,
|
|
394
|
+
// so its entries carry no `path` and resolve to no URL.
|
|
392
395
|
// The address is real — this is not a typo and must not fail the
|
|
393
396
|
// build — but there is nothing on the web to point at, so the
|
|
394
397
|
// reader gets the text and no href. Emitting the href anyway is
|
|
@@ -396,8 +399,8 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
396
399
|
// as a link and goes nowhere.
|
|
397
400
|
const link =
|
|
398
401
|
hit.url ? `[${text}](${anchor ? `${hit.url}#${slugify(anchor)}` : hit.url})` : text;
|
|
399
|
-
// A link into a note that exists but is not written renders marked
|
|
400
|
-
//
|
|
402
|
+
// A link into a note that exists but is not written renders marked.
|
|
403
|
+
// Presentation only — the href above is unchanged, and a
|
|
401
404
|
// `[[#anchor]]` self-link is not marked because the reader is
|
|
402
405
|
// already on the page it would be telling them about.
|
|
403
406
|
return hit.draft ? draftLink(link) : link;
|
|
@@ -413,7 +416,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
413
416
|
// it parses as an address.)
|
|
414
417
|
const siteAddress = prefix !== null && ctx.sections.has(prefix);
|
|
415
418
|
|
|
416
|
-
// **An address resolving nowhere is a failure, unconditionally
|
|
419
|
+
// **An address resolving nowhere is a failure, unconditionally**.
|
|
417
420
|
//
|
|
418
421
|
// It was gated on a manifest-completeness check — while any linkable package was
|
|
419
422
|
// invisible here, `Rules/Bestiary.md` addressing `being-grkrahk` in the
|
|
@@ -443,7 +446,7 @@ export function resolveWebWikilinks(body, ctx) {
|
|
|
443
446
|
: read?.reason === "unknown-type" ? "unknown-type"
|
|
444
447
|
// Every link is an address, and this is not one. Distinct from
|
|
445
448
|
// a dead address, because the fix is different: a name has to
|
|
446
|
-
// become an address, not be corrected
|
|
449
|
+
// become an address, not be corrected.
|
|
447
450
|
: "not-an-address";
|
|
448
451
|
|
|
449
452
|
// Whether or not it failed the build, the link renders marked: the
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* The web side's pattern omitted `\n` from the excluded set, so an unclosed
|
|
30
30
|
* bracket consumed everything up to the next `]]` anywhere in the document —
|
|
31
31
|
* the same shape of corruption a hand-rolled code-fence regex caused on the one
|
|
32
|
-
* page whose subject is link syntax
|
|
32
|
+
* page whose subject is link syntax. It was also internally
|
|
33
33
|
* inconsistent: its *frontmatter* scan excluded newlines while its body scan did
|
|
34
34
|
* not.
|
|
35
35
|
*
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* ({@link LINK_FINDING_REASONS}) and the message each one reports through
|
|
46
46
|
* ({@link linkFindingMessage}). Three builds read one authored link; an author
|
|
47
47
|
* meets whichever ran first, and a consumer switching on a `reason` should not
|
|
48
|
-
* be switching on which build produced it
|
|
48
|
+
* be switching on which build produced it.
|
|
49
49
|
*
|
|
50
50
|
* @module
|
|
51
51
|
*/
|
|
@@ -72,7 +72,7 @@ export const WIKILINK = /\[\[([^\]\n]+)\]\]/g;
|
|
|
72
72
|
* @property {string|null} display - The text after `|`, or `null` when the link
|
|
73
73
|
* is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
|
|
74
74
|
* @property {boolean} labelled - Whether a `|` was present at all. A link
|
|
75
|
-
* without one addresses nothing and is a finding
|
|
75
|
+
* without one addresses nothing and is a finding — see
|
|
76
76
|
* {@link unlabelledLinkMessage} — so every reader has to be able to ask.
|
|
77
77
|
*/
|
|
78
78
|
|
|
@@ -111,12 +111,12 @@ export function parseWikilink(rawInner) {
|
|
|
111
111
|
* `display: null` does. The two are still distinguishable through
|
|
112
112
|
* {@link ParsedWikilink.labelled}, which is the thing that genuinely differs:
|
|
113
113
|
* `[[x|]]` is labelled and `[[x]]` is not, and only the first addresses
|
|
114
|
-
* anything
|
|
114
|
+
* anything.
|
|
115
115
|
*
|
|
116
116
|
* Stated here because the two resolvers had already drawn the line in two
|
|
117
117
|
* places and drawn it differently: the packs tested falsiness and were right,
|
|
118
118
|
* the web tested `??` — which falls through on `null` only — and emitted
|
|
119
|
-
* `[](/url/)`, a link with no clickable text, through every build
|
|
119
|
+
* `[](/url/)`, a link with no clickable text, through every build. That
|
|
120
120
|
* is the same drift this module exists to prevent, in the case its own
|
|
121
121
|
* {@link ParsedWikilink} docstring calls out. One reading, one place.
|
|
122
122
|
*
|
|
@@ -136,12 +136,12 @@ export function authoredLabel({ display }) {
|
|
|
136
136
|
* the form to write rather than a value to correct: there is no value that
|
|
137
137
|
* makes an unlabelled link resolve.
|
|
138
138
|
*
|
|
139
|
-
* **Why there is nothing left for a bare link to mean
|
|
139
|
+
* **Why there is nothing left for a bare link to mean**. The pipe used
|
|
140
140
|
* to select between two namespaces — address and alias — and the alias one was
|
|
141
141
|
* empty in practice: across 8,305 wikilinks in three content trees, not one
|
|
142
142
|
* bare `[[Alias]]` resolved to a note. What the index it looked up in *did* do
|
|
143
143
|
* was fold every note's `name.full` into itself, which forbade two notes of a
|
|
144
|
-
* type from sharing a display name
|
|
144
|
+
* type from sharing a display name. So the namespace is gone, every
|
|
145
145
|
* link is an address, and an address needs the pipe that says so.
|
|
146
146
|
*
|
|
147
147
|
* The **link part may still be an anchor**: `[[#slug|Text]]` addresses a
|
|
@@ -160,16 +160,16 @@ export function unlabelledLinkMessage(target) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
|
-
* Every way a link can fail, named once for all three resolvers
|
|
163
|
+
* Every way a link can fail, named once for all three resolvers.
|
|
164
164
|
*
|
|
165
165
|
* A link is read in three places — the checker (`content-links.mjs`), the pack
|
|
166
166
|
* compilers (`wikilinks.mjs`) and the web resolver (`web-wikilinks.mjs`) — and
|
|
167
|
-
* each
|
|
167
|
+
* each would otherwise name the failures in its own words. `unknown` in one is
|
|
168
168
|
* `unresolved` in another and `broken type/shortcode` in the third, so a
|
|
169
169
|
* consumer switching on a `reason` was switching on which build had produced
|
|
170
170
|
* it. The set is closed and lives here, beside the syntax the three share.
|
|
171
171
|
*
|
|
172
|
-
* - `unlabelled` — no `|`, so the link addresses nothing
|
|
172
|
+
* - `unlabelled` — no `|`, so the link addresses nothing.
|
|
173
173
|
* - `not-an-address` — labelled, but the target does not parse as an address.
|
|
174
174
|
* - `unknown-type` — definitely qualified, but names no type this build knows.
|
|
175
175
|
* - `unresolved` — parses as an address, and nothing publishes it.
|
|
@@ -198,12 +198,12 @@ export const LINK_FINDING_REASONS = Object.freeze(
|
|
|
198
198
|
* package publishing it is not a declared dependency — or is one whose index
|
|
199
199
|
* has not been fetched — and the link itself looks identical in every case.
|
|
200
200
|
*
|
|
201
|
-
*
|
|
201
|
+
* As a **warning** in the checker and, in the site build, nothing
|
|
202
202
|
* at all until every linkable package's manifest was accounted for — on the
|
|
203
203
|
* reasoning that a bare `[[Name]]` might be a placeholder for a note nobody had
|
|
204
|
-
* written yet. That reasoning was a property of the bare form, which is retired
|
|
205
|
-
*
|
|
206
|
-
* that exists and resolves and renders marked
|
|
204
|
+
* written yet. That reasoning was a property of the bare form, which is retired;
|
|
205
|
+
* the intent behind it now has a real spelling, a `draft`-tagged note
|
|
206
|
+
* that exists and resolves and renders marked. So an address naming no
|
|
207
207
|
* note is a typo or an omission, both want fixing, and all three builds say so.
|
|
208
208
|
*
|
|
209
209
|
* @param {string} target - The address as authored, named in the message.
|
|
@@ -226,9 +226,9 @@ export function unresolvedAddressMessage(target) {
|
|
|
226
226
|
* message names the claimants so the author can choose between them without
|
|
227
227
|
* going looking.
|
|
228
228
|
*
|
|
229
|
-
* **The correction is the canonical form**, all four segments
|
|
229
|
+
* **The correction is the canonical form**, all four segments. Omission
|
|
230
230
|
* runs strictly left to right, so there is no `package-type-shortcode` to offer
|
|
231
|
-
* — naming a package means naming the system before the type. That
|
|
231
|
+
* — naming a package means naming the system before the type. That would be
|
|
232
232
|
* the correction here, back when a written target could state three segments at
|
|
233
233
|
* most; the grammar is positional now, and a three-segment target names a
|
|
234
234
|
* *system*, not a package.
|