@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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip the LevelDB keys from a document tree.
|
|
3
|
+
*
|
|
4
|
+
* An Adventure's members are **inline source data** in a `SetField`, not
|
|
5
|
+
* sublevel documents, so they carry no `_key`: the compendium CLI's hierarchy
|
|
6
|
+
* does not recurse into an adventure, and Foundry's schema has no such field to
|
|
7
|
+
* hold one. A member that kept its key would ship a property the data model
|
|
8
|
+
* refuses.
|
|
9
|
+
*
|
|
10
|
+
* Shared with the scenes pass, which bundles the Adventures that make a pinned
|
|
11
|
+
* map's ids resolve — one rule about what an Adventure member may carry, stated
|
|
12
|
+
* once.
|
|
13
|
+
*
|
|
14
|
+
* @param {*} value - A document, array, or scalar.
|
|
15
|
+
* @returns {*} The same shape with every `_key` removed.
|
|
16
|
+
*/
|
|
17
|
+
export function stripAdventureKeys(value: any): any;
|
|
18
|
+
/**
|
|
19
|
+
* An authored address with any wikilink brackets and label stripped.
|
|
20
|
+
*
|
|
21
|
+
* The specification types `contents` as a `WikiLink[]`, and a frontmatter link
|
|
22
|
+
* is written as a bare address — but `[[address]]` is what an author reaches
|
|
23
|
+
* for, and it is what a folder note's `parent` already accepts. Accepting both
|
|
24
|
+
* costs one regex and removes a failure whose message would have to explain the
|
|
25
|
+
* difference.
|
|
26
|
+
*
|
|
27
|
+
* @param {unknown} value - As authored.
|
|
28
|
+
* @returns {string|null} The bare address, or `null` for a blank entry.
|
|
29
|
+
*/
|
|
30
|
+
export function bareAddress(value: unknown): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* The addresses a bundle note names, in the order it names them.
|
|
33
|
+
*
|
|
34
|
+
* `contents` is a `data:` property, which is where the specification's
|
|
35
|
+
* `### type: bundle` table puts it — the closed container, so a misspelled
|
|
36
|
+
* `content` is a finding rather than a silently empty Adventure. It is accepted
|
|
37
|
+
* at the top level too, exactly as a folder note's `parent` is: an author
|
|
38
|
+
* following #259's own example rather than the specification should get a
|
|
39
|
+
* bundle, not a silent default.
|
|
40
|
+
*
|
|
41
|
+
* **Order is the author's**, and it is kept: an Adventure's `SetField` attaches
|
|
42
|
+
* no meaning to member order, but the emitted JSON is compared between runs, so
|
|
43
|
+
* a stable order is what keeps the same tree compiling to the same bytes.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} fm - Parsed frontmatter.
|
|
46
|
+
* @returns {string[]} The bare addresses. Empty when the note states none.
|
|
47
|
+
* @throws {Error} When `contents` is neither absent nor a list — a scalar is a
|
|
48
|
+
* bundle of one written wrongly, and reading it as such would accept a shape
|
|
49
|
+
* the specification does not admit.
|
|
50
|
+
*/
|
|
51
|
+
export function bundleContents(fm: object): string[];
|
|
52
|
+
/**
|
|
53
|
+
* What a pass should do about a member its sources do not hold.
|
|
54
|
+
*
|
|
55
|
+
* The whole of the system rule, in one predicate, because it is a rule and not
|
|
56
|
+
* an implementation detail. An `Adventure` has no `system` field, so a bundle
|
|
57
|
+
* spanning two systems cannot be one document that knows it spans them: it is
|
|
58
|
+
* one Adventure per system, and **the pack each is written to is what carries
|
|
59
|
+
* the system**.
|
|
60
|
+
*
|
|
61
|
+
* The constraint is not computed from the member's *type* — that would be wrong
|
|
62
|
+
* for the types both systems map, which is most of them: a `miscgear` is a SoHL
|
|
63
|
+
* item **and** an HM3 item, so "which system does this type belong to" has no
|
|
64
|
+
* single answer. It is read instead from what the pack can actually see. A pack
|
|
65
|
+
* declaring `system: hm3` reads the HM3 packs and the system-neutral ones, so a
|
|
66
|
+
* note that publishes no HM3 document is simply not in its sources — which is
|
|
67
|
+
* the same fact, established where it is already true.
|
|
68
|
+
*
|
|
69
|
+
* - **The pack declares a system.** A member it cannot see is another system's,
|
|
70
|
+
* and is left **out** — reported, never silently, because an installer that
|
|
71
|
+
* quietly ships half its contents is worse than one that fails.
|
|
72
|
+
* - **The pack declares none.** Nothing is scoped away, so there is no other
|
|
73
|
+
* system for the member to have gone to: its absence is a **failure**.
|
|
74
|
+
*
|
|
75
|
+
* @param {string|null|undefined} packSystem - The pack's declared `system:`.
|
|
76
|
+
* @returns {"omit"|"fail"} What to do about a member the sources lack.
|
|
77
|
+
*/
|
|
78
|
+
export function missingMemberVerdict(packSystem: string | null | undefined): "omit" | "fail";
|
|
79
|
+
/**
|
|
80
|
+
* Assemble one `Adventure` from a set of already-compiled documents.
|
|
81
|
+
*
|
|
82
|
+
* The members are **copies**, and they arrive compiled: this takes the JSON a
|
|
83
|
+
* previous pass wrote and files each document under the content field its class
|
|
84
|
+
* maps to. Nothing is derived from the content tree here, which is what keeps
|
|
85
|
+
* this half framework-free and testable without one.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} params
|
|
88
|
+
* @param {string} params.id - The Adventure's `_id`.
|
|
89
|
+
* @param {string} params.name - Its name.
|
|
90
|
+
* @param {string|null} [params.img] - Its artwork, or `null`.
|
|
91
|
+
* @param {string} [params.description] - The note's prose, already rendered.
|
|
92
|
+
* @param {string} [params.caption] - The short caption Foundry shows on the
|
|
93
|
+
* import card. Blank unless a caller has one.
|
|
94
|
+
* @param {string|null} [params.folder] - The folder id it is filed under.
|
|
95
|
+
* @param {object} [params.flags] - Document flags.
|
|
96
|
+
* @param {object} params.stats - The `_stats` block to stamp.
|
|
97
|
+
* @param {ReadonlyArray<{docType: string, document: object}>} params.contents -
|
|
98
|
+
* The compiled documents it holds, each with the Foundry class it is.
|
|
99
|
+
* @returns {object} The Adventure document, keyed for the pack.
|
|
100
|
+
* @throws {Error} When a member's document class is not one an Adventure can
|
|
101
|
+
* hold — a defect in the caller rather than in the note.
|
|
102
|
+
*/
|
|
103
|
+
export function buildAdventure({ id, name, img, description, caption, folder, flags, stats, contents, }: {
|
|
104
|
+
id: string;
|
|
105
|
+
name: string;
|
|
106
|
+
img?: string | null | undefined;
|
|
107
|
+
description?: string | undefined;
|
|
108
|
+
caption?: string | undefined;
|
|
109
|
+
folder?: string | null | undefined;
|
|
110
|
+
flags?: object | undefined;
|
|
111
|
+
stats: object;
|
|
112
|
+
contents: ReadonlyArray<{
|
|
113
|
+
docType: string;
|
|
114
|
+
document: object;
|
|
115
|
+
}>;
|
|
116
|
+
}): object;
|
|
117
|
+
/**
|
|
118
|
+
* Bundles, as notes — the Foundry `Adventure` a `type: bundle` note compiles
|
|
119
|
+
* into.
|
|
120
|
+
*
|
|
121
|
+
* An `Adventure` is badly named, and the name misled the first design: it is
|
|
122
|
+
* not a story. `Adventure.contentFields` maps each `SetField` on the schema to
|
|
123
|
+
* a document class, and importing one partitions its members by whether the
|
|
124
|
+
* world's collection already holds that `_id`, then creates or updates each.
|
|
125
|
+
* Afterwards the documents live independently and the Adventure has no further
|
|
126
|
+
* role. **It is an installer** — a set of document *copies* packaged for
|
|
127
|
+
* one-shot import (#259).
|
|
128
|
+
*
|
|
129
|
+
* That is the whole difference from a folder (#256), which is a live grouping
|
|
130
|
+
* **by reference** that persists in the pack. A bundle carries copies and
|
|
131
|
+
* exists to be imported once, so the two are not variations on one idea.
|
|
132
|
+
*
|
|
133
|
+
* The note type is `bundle` rather than `adventure` because the format prefers
|
|
134
|
+
* the domain word wherever Foundry's misleads — a `Scene` is authored as a
|
|
135
|
+
* `map` — and `collection` was refused because `DocumentCollection` is a real
|
|
136
|
+
* Foundry class meaning very nearly the opposite. See `docs/content-format.md`
|
|
137
|
+
* § `type: bundle`.
|
|
138
|
+
*
|
|
139
|
+
* This module is the framework-free half: what a bundle note *says*, and how a
|
|
140
|
+
* set of already-compiled documents becomes an Adventure. The pass that walks
|
|
141
|
+
* the tree and resolves what one note says about another is
|
|
142
|
+
* {@link module:engine/bundles}.
|
|
143
|
+
*
|
|
144
|
+
* @module
|
|
145
|
+
*/
|
|
146
|
+
/**
|
|
147
|
+
* The note type a bundle is authored as.
|
|
148
|
+
*
|
|
149
|
+
* @type {string}
|
|
150
|
+
*/
|
|
151
|
+
export const BUNDLE_TYPE: string;
|
|
152
|
+
/**
|
|
153
|
+
* Foundry's `Adventure.contentFields`, keyed by the document class each holds.
|
|
154
|
+
*
|
|
155
|
+
* Restated here rather than derived, because this build never loads Foundry:
|
|
156
|
+
* the schema is read from `common/documents/adventure.mjs`, where every
|
|
157
|
+
* `SetField` of an `EmbeddedDataField` is a content field and its name is the
|
|
158
|
+
* key an importer partitions on. The inverse direction is the one a compiler
|
|
159
|
+
* wants — it has a document type in hand and needs the field to file it under.
|
|
160
|
+
*
|
|
161
|
+
* The full set is listed, not only the five this build compiles, because the
|
|
162
|
+
* map is a statement about Foundry rather than about this toolchain: a consumer
|
|
163
|
+
* registering a compiler for `RollTable` needs no edit here.
|
|
164
|
+
*
|
|
165
|
+
* @type {Readonly<Record<string, string>>}
|
|
166
|
+
*/
|
|
167
|
+
export const ADVENTURE_CONTENT_FIELD: Readonly<Record<string, string>>;
|
|
168
|
+
/**
|
|
169
|
+
* The `data:` key a bundle lists its members under.
|
|
170
|
+
*
|
|
171
|
+
* @type {string}
|
|
172
|
+
*/
|
|
173
|
+
export const CONTENTS_FIELD: string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load every compiled document a bundle may hold, keyed `<docType>/<id>`.
|
|
3
|
+
*
|
|
4
|
+
* Keyed by id rather than by address because that is what the note resolves to:
|
|
5
|
+
* an address names a note, the note's id is derived once by `resolveNoteId`,
|
|
6
|
+
* and every pass files its document under it. Matching on the id is therefore
|
|
7
|
+
* an identity check rather than a second derivation that could disagree with
|
|
8
|
+
* the first.
|
|
9
|
+
*
|
|
10
|
+
* A **folder** document is skipped. It is emitted into every pack that holds
|
|
11
|
+
* something filed in it (#257), so it is not one pack's document and has no
|
|
12
|
+
* single note behind it; a bundle that wants folders is a question this pass
|
|
13
|
+
* refuses rather than guesses at — see {@link Bundles#resolveAddress}.
|
|
14
|
+
*
|
|
15
|
+
* @param {Readonly<Record<string, readonly string[]>>} sourceDirs - The JSON
|
|
16
|
+
* directories of every pack whose output may be bundled, by document type.
|
|
17
|
+
* @returns {Map<string, object>} The compiled documents.
|
|
18
|
+
* @throws {Error} When a declared directory does not exist — the generator
|
|
19
|
+
* orders this pass last, so what reaches this is a run restricted to one
|
|
20
|
+
* pack, which reordering cannot fix.
|
|
21
|
+
*/
|
|
22
|
+
export function loadBundleSources(sourceDirs: Readonly<Record<string, readonly string[]>>): Map<string, object>;
|
|
23
|
+
export class Bundles extends BasePackCompiler {
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} options - As {@link BasePackCompiler}, plus:
|
|
26
|
+
* @param {Record<string, readonly string[]>} [options.bundleSourceDirs] -
|
|
27
|
+
* Each bundleable pack's JSON tree, by document type. Supplied by the
|
|
28
|
+
* generator from the configured pack list, so the dependency is stated
|
|
29
|
+
* rather than assumed from a sibling directory.
|
|
30
|
+
*/
|
|
31
|
+
constructor({ bundleSourceDirs, ...options }: {
|
|
32
|
+
bundleSourceDirs?: Record<string, readonly string[]> | undefined;
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* The JSON directories this pass reads its members from, by document type.
|
|
36
|
+
*
|
|
37
|
+
* @type {Readonly<Record<string, readonly string[]>>}
|
|
38
|
+
*/
|
|
39
|
+
bundleSourceDirs: Readonly<Record<string, readonly string[]>>;
|
|
40
|
+
members: Map<string, object> | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* The document one address in `contents` names.
|
|
43
|
+
*
|
|
44
|
+
* The address alone, resolved against the content tree — whether this pack
|
|
45
|
+
* actually *holds* that document is the caller's question, and the answer
|
|
46
|
+
* to it is what the system rule turns on.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} address - One bare address from `contents`.
|
|
49
|
+
* @param {string} bundleName - The bundle, for the message.
|
|
50
|
+
* @returns {{docType: string, id: string, name: string}} The document the
|
|
51
|
+
* address names.
|
|
52
|
+
* @throws {Error} When the address names nothing this build compiles.
|
|
53
|
+
*/
|
|
54
|
+
resolveAddress(address: string, bundleName: string): {
|
|
55
|
+
docType: string;
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fence's info string, read as **org-babel header arguments**.
|
|
3
|
+
*
|
|
4
|
+
* A directive fence carries statements *about the directive* that are no part of
|
|
5
|
+
* the query it holds — whether an empty result is intended, what heading level a
|
|
6
|
+
* section takes, later a caption. Those started as an ad-hoc bare word
|
|
7
|
+
* (`allow-empty`) and a `key=value` (`section-level=3`), each matched by its own
|
|
8
|
+
* regex: a grammar only in the sense that two regexes are one, and with no room
|
|
9
|
+
* to add a third property without adding a third spelling.
|
|
10
|
+
*
|
|
11
|
+
* Org-mode settled this long ago. A babel source block writes them after the
|
|
12
|
+
* language as `:key value`, which is a real grammar with a specification, a
|
|
13
|
+
* parser, and an editor that already completes it.
|
|
14
|
+
*
|
|
15
|
+
* ```text
|
|
16
|
+
* ```sql :section-level 3 :allow-empty
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* **The language word stays first and stays plain.** `sql` is what GitHub,
|
|
20
|
+
* Prettier and every other markdown reader match on to syntax-highlight the
|
|
21
|
+
* block, so it leads and the header args follow; a reader that does not know
|
|
22
|
+
* them sees an ordinary SQL block.
|
|
23
|
+
*
|
|
24
|
+
* The grammar, matching org's:
|
|
25
|
+
*
|
|
26
|
+
* - a key is `:name` **starting a word** — a colon inside or ending a word is
|
|
27
|
+
* text, so `:caption Gear: the tables` is one argument;
|
|
28
|
+
* - a value runs to the next key or the end of the string, spaces included, and
|
|
29
|
+
* is trimmed;
|
|
30
|
+
* - a key with no value is `true`, which is what a statement like
|
|
31
|
+
* `:allow-empty` is;
|
|
32
|
+
* - a value may be `"quoted"` to hold a word that would otherwise read as a
|
|
33
|
+
* key — the one ambiguity org has too;
|
|
34
|
+
* - a repeated key takes its last value.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} info - The text after the fence marker.
|
|
37
|
+
* @returns {{language: string, args: Record<string, string|true>}} The language,
|
|
38
|
+
* lowercased, and the header arguments in written order.
|
|
39
|
+
*/
|
|
40
|
+
export function parseHeaderArgs(info: string): {
|
|
41
|
+
language: string;
|
|
42
|
+
args: Record<string, string | true>;
|
|
43
|
+
};
|
|
1
44
|
/**
|
|
2
45
|
* Every code region in a markdown body, as character offsets into it.
|
|
3
46
|
*
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the corpus a compile runs over, and the three indexes built over it.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} opts - Options.
|
|
5
|
+
* @param {string} opts.contentBase - Root of the content tree.
|
|
6
|
+
* @param {readonly string[]} opts.skipDirectories - The scope, stated by the
|
|
7
|
+
* caller as every corpus read requires (#243).
|
|
8
|
+
* @param {object} opts.router - The pack router this compile resolved. Shared
|
|
9
|
+
* by every pass, which is what makes one link index correct for all of them.
|
|
10
|
+
* @param {object} [opts.config] - The resolved configuration.
|
|
11
|
+
* @param {object[]} [opts.problems] - Collects the notes the index cannot
|
|
12
|
+
* record, so one of them does not abort the compile before it reports. One is
|
|
13
|
+
* created when none is passed, and returned on the corpus either way — a note
|
|
14
|
+
* the index refuses is a note the compile must still *report*, exactly as the
|
|
15
|
+
* compile loop reported it when the loop was the first to see it.
|
|
16
|
+
* @returns {Promise<{records: object[], linkIndex: object, contentDocs: object[],
|
|
17
|
+
* sqlTables: Map<string, object[]>|undefined, problems: object[]}>} The corpus,
|
|
18
|
+
* its indexes, and the notes it could not record.
|
|
19
|
+
*/
|
|
20
|
+
export function buildCompileCorpus({ contentBase, skipDirectories, router, config, problems, }: {
|
|
21
|
+
contentBase: string;
|
|
22
|
+
skipDirectories: readonly string[];
|
|
23
|
+
router: object;
|
|
24
|
+
config?: object | undefined;
|
|
25
|
+
problems?: object[] | undefined;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
records: object[];
|
|
28
|
+
linkIndex: object;
|
|
29
|
+
contentDocs: object[];
|
|
30
|
+
sqlTables: Map<string, object[]> | undefined;
|
|
31
|
+
problems: object[];
|
|
32
|
+
}>;
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The single path segment a note is addressed by: `type-shortcode`.
|
|
3
3
|
*
|
|
4
|
-
* Lowercased
|
|
5
|
-
* (
|
|
6
|
-
* makes a manifest entry's `path`
|
|
7
|
-
* rather than transported beside it.
|
|
4
|
+
* Lowercased and hyphen-joined by the same rule as the note's canonical key
|
|
5
|
+
* ({@link canonicalKey}, below, lowercases too), so it is that
|
|
6
|
+
* key's **last two segments** — which is what makes a manifest entry's `path`
|
|
7
|
+
* derivable from the key it is filed under rather than transported beside it.
|
|
8
|
+
*
|
|
9
|
+
* It was once the key's whole tail, and #59 ended that: the key gained a
|
|
10
|
+
* `<system>` segment, so its tail is now `system-type-shortcode` and a slug is
|
|
11
|
+
* the tail with that segment dropped. The behaviour here is unchanged, and
|
|
12
|
+
* deliberately — a page has no system to name (see the module note above), so
|
|
13
|
+
* the two forms diverge rather than one having fallen behind the other. A
|
|
14
|
+
* consumer deriving a `path` from a key drops the *package and the system*, not
|
|
15
|
+
* the package alone.
|
|
8
16
|
*
|
|
9
17
|
* The hyphen is a separator and never occurs inside a segment: a shortcode is
|
|
10
18
|
* `^[A-Za-z0-9]+$` (`ADDRESS_SEGMENT_PATTERN`, enforced by `content-lint.mjs`)
|
|
@@ -56,7 +64,199 @@ export function addressSlug(fm: object): string;
|
|
|
56
64
|
* dead entry in the manifest.
|
|
57
65
|
*/
|
|
58
66
|
export function packageAddress(fm: object): string;
|
|
59
|
-
|
|
67
|
+
/**
|
|
68
|
+
* The **canonical** address of a note: fully qualified, one spelling per
|
|
69
|
+
* document, and globally unique.
|
|
70
|
+
*
|
|
71
|
+
* The written form of a link is a **partial** address: it may omit leading
|
|
72
|
+
* segments, and each one it omits is filled in by rule rather than left
|
|
73
|
+
* unconstrained. An omitted package (`[[skill-lang]]`) defaults to the citing
|
|
74
|
+
* note's own, so an unqualified link resolves locally and only locally, and a
|
|
75
|
+
* link into another package must name it. An omitted **system** is a
|
|
76
|
+
* *wildcard*, not a default — most links target items, which belong to a
|
|
77
|
+
* system — and the resolver requires exactly one match: none is a dead link,
|
|
78
|
+
* more than one is an ambiguity reported with every candidate named.
|
|
79
|
+
* Everything internal — index keys, cache keys, every lookup — uses this fully
|
|
80
|
+
* qualified form instead, so no consumer has to know what a short form
|
|
81
|
+
* defaulted to or matched.
|
|
82
|
+
*
|
|
83
|
+
* Global uniqueness is what lets a dependency's index merge straight into a
|
|
84
|
+
* local one: the keys cannot collide by accident, so a key already present on
|
|
85
|
+
* merge is a real conflict rather than an artefact of two packages sharing a
|
|
86
|
+
* namespace. `(type, shortcode)` alone is unique only *within* a package, and
|
|
87
|
+
* two independently authored packages reaching for the same short string is a
|
|
88
|
+
* matter of time (#1499).
|
|
89
|
+
*
|
|
90
|
+
* **The system segment (#59).** A package may ship content for more than one
|
|
91
|
+
* system, and one note then compiles into a document per system — an actor in
|
|
92
|
+
* `actors-sohl` *and* an actor in `actors-hm3`. Without a system segment both
|
|
93
|
+
* land on one key, so the address cannot name either of them. `harn-ensemble`
|
|
94
|
+
* carries 2,497 such notes.
|
|
95
|
+
*
|
|
96
|
+
* The value is a system id, or the literal **`none`** for a document no game
|
|
97
|
+
* system defines: a journal, a macro, a scene, and an item's documentation
|
|
98
|
+
* journal — which is `none` however many systems the item itself declares,
|
|
99
|
+
* because it is one journal.
|
|
100
|
+
*
|
|
101
|
+
* `none` rather than `any`: every segment of an address is an exact literal,
|
|
102
|
+
* and `any` reads as a wildcard — "matches under any system" — which is not
|
|
103
|
+
* what it does. A resolver written to that misreading would fail silently,
|
|
104
|
+
* since a lookup miss already returns nothing rather than erroring. And not
|
|
105
|
+
* `null` or `~`, both of which are YAML nulls that parse to an absent value and
|
|
106
|
+
* drop the segment entirely.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} pkg - The owning **content** package (`sohl`, `thalorna`) —
|
|
109
|
+
* not the Foundry package, which varies per compilation target.
|
|
110
|
+
* @param {string} system - The system whose document this addresses, or `none`.
|
|
111
|
+
* @param {string} type - The note's `type`.
|
|
112
|
+
* @param {string} shortcode - The note's `shortcode`.
|
|
113
|
+
* @returns {string} `package-system-type-shortcode`, lowercased.
|
|
114
|
+
*/
|
|
115
|
+
export function canonicalKey(pkg: string, system: string, type: string, shortcode: string): string;
|
|
116
|
+
/**
|
|
117
|
+
* Reads a canonical key back into its parts.
|
|
118
|
+
*
|
|
119
|
+
* Parsing is plain positional counting: split on the separator, require
|
|
120
|
+
* {@link CANONICAL_KEY_SEGMENTS} of them, and assign each position its field.
|
|
121
|
+
* **The charset rule is what makes that sound** — every segment is
|
|
122
|
+
* `^[A-Za-z0-9]+$` (`ADDRESS_SEGMENT_PATTERN` in `engine/address-charset.mjs`),
|
|
123
|
+
* so the hyphen is purely a separator and the count alone determines every
|
|
124
|
+
* field. That is enforced at each of the three sources rather than assumed of
|
|
125
|
+
* the data: shortcodes by `content-lint.mjs` (#1397), `contentPackage` by
|
|
126
|
+
* `defineConfig` (#59), and types are bare words. Were any of them free to
|
|
127
|
+
* carry a hyphen, no amount of counting would recover the fields and the reader
|
|
128
|
+
* would need a vocabulary to match against instead.
|
|
129
|
+
*
|
|
130
|
+
* **Nothing to read and nothing readable are different answers.** A key that
|
|
131
|
+
* cannot be canonical — `harn-adventures-sohl-skill-melee`, five segments,
|
|
132
|
+
* because the package name carries the separator — yields `null`, while an
|
|
133
|
+
* absent or blank input yields `undefined`. Both are falsy, so
|
|
134
|
+
* every call site (all of which test the result for truthiness) is unaffected;
|
|
135
|
+
* the distinction is there so a caller reporting "this key is unreadable" can
|
|
136
|
+
* tell that it has a key to report about.
|
|
137
|
+
*
|
|
138
|
+
* @param {unknown} key - A canonical key, or nothing.
|
|
139
|
+
* @returns {{package: string, system: string, type: string, shortcode: string}
|
|
140
|
+
* |null|undefined}
|
|
141
|
+
* The parts; `null` when there is a string that is not in canonical form;
|
|
142
|
+
* `undefined` when there is no key at all.
|
|
143
|
+
*/
|
|
144
|
+
export function readCanonicalKey(key: unknown): {
|
|
145
|
+
package: string;
|
|
146
|
+
system: string;
|
|
147
|
+
type: string;
|
|
148
|
+
shortcode: string;
|
|
149
|
+
} | null | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Asserts a base is usable as a prefix and returns it.
|
|
152
|
+
*
|
|
153
|
+
* Exported for the URL helpers' own callers: a build that composes a base
|
|
154
|
+
* before resolving against it should reject a malformed one at the point it
|
|
155
|
+
* is chosen, not at the point a link is emitted.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} base - The package base.
|
|
158
|
+
* @param {string} what - What is being resolved, for the error message.
|
|
159
|
+
* @returns {string} The base.
|
|
160
|
+
*/
|
|
161
|
+
export function checkBase(base: string, what: string): string;
|
|
162
|
+
/**
|
|
163
|
+
* The package-relative address a site-absolute URL records as.
|
|
164
|
+
*
|
|
165
|
+
* Strips the emitting package's own base, so what is recorded says *where in
|
|
166
|
+
* the package* a page is and nothing about where the package itself is
|
|
167
|
+
* mounted. A URL outside the base is an error rather than a best effort: it
|
|
168
|
+
* would record an address that silently resolves to the wrong place once a
|
|
169
|
+
* consumer prefixes its own base.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} url - The site-absolute URL the emitting build publishes at.
|
|
172
|
+
* @param {string} base - That build's base for the package, e.g. `"/thalorna/"`.
|
|
173
|
+
* @returns {string} The address relative to `base`, with no leading slash.
|
|
174
|
+
*/
|
|
175
|
+
export function packageRelative(url: string, base: string): string;
|
|
176
|
+
/**
|
|
177
|
+
* The URL a package-relative address resolves to in this build.
|
|
178
|
+
*
|
|
179
|
+
* Plain concatenation, which is what makes an absolute-origin base work: a base
|
|
180
|
+
* of `"https://thalorna.example.org/"` yields an absolute link, and one of
|
|
181
|
+
* `"/thalorna/"` a root-relative one, with no other rule to keep in step.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} rel - The package-relative address from a fetched index.
|
|
184
|
+
* @param {string} base - This build's base for that package.
|
|
185
|
+
* @returns {string} The resolved URL.
|
|
186
|
+
*/
|
|
187
|
+
export function resolvePackageUrl(rel: string, base: string): string;
|
|
188
|
+
/**
|
|
189
|
+
* The Foundry `_id` of the document a note compiles into, derived from its
|
|
190
|
+
* canonical address (#270).
|
|
191
|
+
*
|
|
192
|
+
* A note used to author this — an opaque 16-character string, one per note,
|
|
193
|
+
* that said nothing its address did not and that no check guaranteed. The
|
|
194
|
+
* address is the identity that *is* guaranteed: `content-lint` refuses a
|
|
195
|
+
* duplicate `(type, shortcode)` across every pack of a document type, which is
|
|
196
|
+
* exactly the scope a primary document's id must be unique within. So the
|
|
197
|
+
* derived id inherits a guard that already exists, where the authored one had
|
|
198
|
+
* none.
|
|
199
|
+
*
|
|
200
|
+
* **The coupling this creates, stated plainly.** The address carries the
|
|
201
|
+
* shortcode, so renaming a shortcode moves the document's id — where an
|
|
202
|
+
* authored id survived one. That is a real trade rather than a free win, and
|
|
203
|
+
* two things make it acceptable: a rename already breaks every wikilink to the
|
|
204
|
+
* note, so it is a breaking change either way; and a note that must keep its
|
|
205
|
+
* identity across a rename pins an `id`, which is what the pin is for. One
|
|
206
|
+
* thing genuinely degrades — `engine/address-diff.mjs` tells a rename from a
|
|
207
|
+
* withdrawal by matching document ids, and can no longer do so for a note that
|
|
208
|
+
* authors none. Its module note records that.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} pkg - The owning **content** package.
|
|
211
|
+
* @param {string} system - The system whose document this is, or `none`.
|
|
212
|
+
* @param {string} type - The note's `type`.
|
|
213
|
+
* @param {string} shortcode - The note's `shortcode`.
|
|
214
|
+
* @returns {string} A 16-character Foundry id.
|
|
215
|
+
*/
|
|
216
|
+
export function documentId(pkg: string, system: string, type: string, shortcode: string): string;
|
|
60
217
|
/** The knowledgebase's mount within this package's site (#1470). */
|
|
61
218
|
export const KB_PREFIX: "kb/";
|
|
219
|
+
/**
|
|
220
|
+
* How many segments a canonical key has, and therefore how many the reader
|
|
221
|
+
* below counts.
|
|
222
|
+
*
|
|
223
|
+
* Named rather than written as a literal because it is the *grammar*, not an
|
|
224
|
+
* implementation detail of one function: it is the number a change to the
|
|
225
|
+
* address form would move, and the thing a reader of that change has to find.
|
|
226
|
+
*
|
|
227
|
+
* @type {number}
|
|
228
|
+
*/
|
|
229
|
+
export const CANONICAL_KEY_SEGMENTS: number;
|
|
230
|
+
/**
|
|
231
|
+
* Where this build serves each package, keyed by package name.
|
|
232
|
+
*
|
|
233
|
+
* One line per package, and the only edit a relocation requires: point a
|
|
234
|
+
* package at another path (`"/setting/thalorna/"`) or another origin
|
|
235
|
+
* (`"https://thalorna.example.org/"`) and every inbound link into it follows.
|
|
236
|
+
* A base is a prefix, so it must end in `/`.
|
|
237
|
+
*
|
|
238
|
+
* Only *foreign* packages are consulted — a package this build publishes is
|
|
239
|
+
* authoritative in its own entries and never resolves through a fetched
|
|
240
|
+
* index — but every linkable package is listed, because which are foreign
|
|
241
|
+
* depends on the consuming repository.
|
|
242
|
+
*/
|
|
243
|
+
export const PACKAGE_BASE: Readonly<{
|
|
244
|
+
sohl: "/sohl/";
|
|
245
|
+
thalorna: "/thalorna/";
|
|
246
|
+
}>;
|
|
247
|
+
/**
|
|
248
|
+
* The namespace {@link documentId} hashes a canonical address under.
|
|
249
|
+
*
|
|
250
|
+
* Named rather than written as a literal at the one call site, because it is
|
|
251
|
+
* part of the published derivation: a consumer holding a content-index entry
|
|
252
|
+
* recomputes the document's id — and therefore its compendium UUID — as
|
|
253
|
+
* `makeId(DOCUMENT_ID_NAMESPACE, entry.canonical)`, so the string is a fact
|
|
254
|
+
* about the format rather than an implementation detail. Changing it moves
|
|
255
|
+
* every id this toolchain has ever emitted.
|
|
256
|
+
*
|
|
257
|
+
* @type {string}
|
|
258
|
+
*/
|
|
259
|
+
export const DOCUMENT_ID_NAMESPACE: string;
|
|
62
260
|
import { DEFAULT_ADDRESS_SCHEME } from "../content-config.mjs";
|
|
261
|
+
import { NO_SYSTEM } from "./systems.mjs";
|
|
262
|
+
export { DEFAULT_ADDRESS_SCHEME, NO_SYSTEM };
|
|
@@ -20,10 +20,14 @@ export function declaredPaths(artifact: object): Set<string>;
|
|
|
20
20
|
* {@link undeclaredMessage} draws, and the difference between "the
|
|
21
21
|
* specification is wrong" and "the schema has not caught up".
|
|
22
22
|
*
|
|
23
|
-
*
|
|
23
|
+
* A shared row names no type, so it is placed rather than typed: "in the shared
|
|
24
|
+
* mappings" instead of "on a `weapongear`". Reading `on a \`the shared
|
|
25
|
+
* mappings\`` would be the alternative, and a diagnostic is prose (#275).
|
|
26
|
+
*
|
|
27
|
+
* @param {object} finding - `{system, systemVersion, noteType, source, target, shared}`.
|
|
24
28
|
* @returns {string} The message.
|
|
25
29
|
*/
|
|
26
|
-
export function undeclaredTargetMessage({ system, systemVersion, noteType, source, target }: object): string;
|
|
30
|
+
export function undeclaredTargetMessage({ system, systemVersion, noteType, source, target, shared, }: object): string;
|
|
27
31
|
/**
|
|
28
32
|
* Check every `system.*` target the specification names.
|
|
29
33
|
*
|
|
@@ -30,6 +30,53 @@ export function loadContentFormat(file?: string): ContentFormat;
|
|
|
30
30
|
* @type {string}
|
|
31
31
|
*/
|
|
32
32
|
export const CONTENT_FORMAT_PATH: string;
|
|
33
|
+
/**
|
|
34
|
+
* What one note type's section declares.
|
|
35
|
+
*
|
|
36
|
+
* @typedef {object} TypeSpec
|
|
37
|
+
* @property {string} name - The note type, as the `### type:` heading spells it.
|
|
38
|
+
* @property {number} line - 1-based line of that heading.
|
|
39
|
+
* @property {Set<string>} dataKeys - The head segment of each declared `data`
|
|
40
|
+
* property — what a note actually writes. `appearance.eye_color` is authored
|
|
41
|
+
* as `appearance`, so that is the key recorded.
|
|
42
|
+
* @property {Set<string>} dataPaths - The declared paths, whole.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* One `system.*` target the specification names for one note type.
|
|
46
|
+
*
|
|
47
|
+
* @typedef {object} MappingClaim
|
|
48
|
+
* @property {string} noteType - The type whose section makes the claim, or
|
|
49
|
+
* `the shared mappings` for a row of the shared tables — see `shared`.
|
|
50
|
+
* @property {boolean} [shared] - Whether the row came from a **shared** mapping
|
|
51
|
+
* table, which stands before the first `### type:` heading and states what
|
|
52
|
+
* every type maps identically (#275). Absent on a per-type row, so the two
|
|
53
|
+
* never mix: only a per-type row has a field declaration to be checked
|
|
54
|
+
* against.
|
|
55
|
+
* @property {string} system - The system column it sits under, from the header.
|
|
56
|
+
* @property {string} source - The shared source cell, stripped of its backticks.
|
|
57
|
+
* @property {string} target - The dotted path, `system.` prefix included.
|
|
58
|
+
* @property {number} line - 1-based line of the row.
|
|
59
|
+
* @property {number} column - 1-based column of the cell's first character.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* The specification, as data.
|
|
63
|
+
*
|
|
64
|
+
* @typedef {object} ContentFormat
|
|
65
|
+
* @property {string} file - Where it was read from, for diagnostics.
|
|
66
|
+
* @property {Map<string, TypeSpec>} types - Note type → what its section declares.
|
|
67
|
+
* @property {MappingClaim[]} claims - Every `system.*` target, in document order.
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* What a shared row's `noteType` reads, in place of a type name.
|
|
71
|
+
*
|
|
72
|
+
* Phrased to be substituted into a diagnostic sentence — "the format maps `x`
|
|
73
|
+
* in the shared mappings to `y`" — because that is the only place it is ever
|
|
74
|
+
* read. Both shared tables use it: the second states what the actor types add,
|
|
75
|
+
* and a row of it is no more a `being`'s than a row of the first is.
|
|
76
|
+
*
|
|
77
|
+
* @type {string}
|
|
78
|
+
*/
|
|
79
|
+
export const SHARED_SCOPE: string;
|
|
33
80
|
/**
|
|
34
81
|
* What one note type's section declares.
|
|
35
82
|
*/
|
|
@@ -58,9 +105,18 @@ export type TypeSpec = {
|
|
|
58
105
|
*/
|
|
59
106
|
export type MappingClaim = {
|
|
60
107
|
/**
|
|
61
|
-
* - The type whose section makes the claim
|
|
108
|
+
* - The type whose section makes the claim, or
|
|
109
|
+
* `the shared mappings` for a row of the shared tables — see `shared`.
|
|
62
110
|
*/
|
|
63
111
|
noteType: string;
|
|
112
|
+
/**
|
|
113
|
+
* - Whether the row came from a **shared** mapping
|
|
114
|
+
* table, which stands before the first `### type:` heading and states what
|
|
115
|
+
* every type maps identically (#275). Absent on a per-type row, so the two
|
|
116
|
+
* never mix: only a per-type row has a field declaration to be checked
|
|
117
|
+
* against.
|
|
118
|
+
*/
|
|
119
|
+
shared?: boolean | undefined;
|
|
64
120
|
/**
|
|
65
121
|
* - The system column it sits under, from the header.
|
|
66
122
|
*/
|