@heroiclands/package-build 0.6.0 → 3.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-content-build.md +965 -0
- package/CHANGELOG.md +95 -0
- package/CONTENT.md +824 -0
- package/MIGRATING.md +127 -0
- package/README.md +29 -17
- package/bin/content-build.mjs +1082 -0
- package/bin/package-build.mjs +2 -2
- package/bin/report.mjs +2 -2
- package/config.mjs +21 -18
- package/content-config.mjs +1424 -0
- package/coverage.mjs +1 -1
- package/e2e.mjs +1 -1
- package/engine/abbreviations.mjs +0 -0
- package/engine/base-compiler.mjs +608 -0
- package/engine/code-fences.mjs +311 -0
- package/engine/compendiums.mjs +353 -0
- package/engine/content-address.mjs +141 -0
- package/engine/content-links.mjs +438 -0
- package/engine/content-lint.mjs +204 -0
- package/engine/content-package.mjs +67 -0
- package/engine/content-slug.mjs +169 -0
- package/engine/content-tables.mjs +1216 -0
- package/engine/content-tree.mjs +56 -0
- package/engine/diagnostics.mjs +270 -0
- package/engine/field-reference.mjs +242 -0
- package/engine/field-spec.mjs +218 -0
- package/engine/foreign-catalog.mjs +450 -0
- package/engine/foreign-manifests.mjs +129 -0
- package/engine/frontmatter-lint.mjs +343 -0
- package/engine/frontmatter.mjs +258 -0
- package/engine/generate.mjs +305 -0
- package/engine/helpers.mjs +809 -0
- package/engine/ids.mjs +192 -0
- package/engine/index.mjs +142 -0
- package/engine/item-docs.mjs +153 -0
- package/engine/item-registry.mjs +129 -0
- package/engine/journals.mjs +374 -0
- package/engine/kb-manifest.mjs +473 -0
- package/engine/macros.mjs +333 -0
- package/engine/manifest-emit.mjs +339 -0
- package/engine/map-notes.mjs +1302 -0
- package/engine/pack-config.mjs +479 -0
- package/engine/pack-router.mjs +254 -0
- package/engine/prose-config.mjs +238 -0
- package/engine/prose-lint.mjs +294 -0
- package/engine/region-events.mjs +79 -0
- package/engine/scene-levels.mjs +420 -0
- package/engine/scenes.mjs +528 -0
- package/engine/site-build.mjs +778 -0
- package/engine/site-index.mjs +332 -0
- package/engine/web-wikilinks.mjs +330 -0
- package/engine/wikilink-syntax.mjs +105 -0
- package/engine/wikilinks.mjs +645 -0
- package/index.mjs +42 -25
- package/lang.mjs +1 -1
- package/manifest.mjs +79 -5
- package/markdownlint-config.mjs +45 -0
- package/package.json +78 -34
- package/prettier-config.mjs +34 -0
- package/sohl/actors.mjs +581 -0
- package/sohl/affiliation-standings.mjs +44 -0
- package/sohl/being-info.mjs +173 -0
- package/sohl/default-item-art.mjs +82 -0
- package/sohl/index.mjs +55 -0
- package/sohl/item-builders.mjs +114 -0
- package/sohl/item-fields.mjs +867 -0
- package/sohl/items.mjs +197 -0
- package/sohl/kb-passes.mjs +207 -0
- package/sohl/note-schemas.mjs +322 -0
- package/templates.mjs +1 -1
- package/types/config.d.mts +1 -1
- package/types/content-config.d.mts +571 -0
- package/types/engine/abbreviations.d.mts +44 -0
- package/types/engine/base-compiler.d.mts +320 -0
- package/types/engine/code-fences.d.mts +102 -0
- package/types/engine/compendiums.d.mts +77 -0
- package/types/engine/content-address.d.mts +61 -0
- package/types/engine/content-links.d.mts +87 -0
- package/types/engine/content-lint.d.mts +47 -0
- package/types/engine/content-package.d.mts +36 -0
- package/types/engine/content-slug.d.mts +59 -0
- package/types/engine/content-tables.d.mts +131 -0
- package/types/engine/content-tree.d.mts +12 -0
- package/types/engine/diagnostics.d.mts +152 -0
- package/types/engine/field-reference.d.mts +20 -0
- package/types/engine/field-spec.d.mts +181 -0
- package/types/engine/foreign-catalog.d.mts +103 -0
- package/types/engine/foreign-manifests.d.mts +43 -0
- package/types/engine/frontmatter-lint.d.mts +74 -0
- package/types/engine/frontmatter.d.mts +99 -0
- package/types/engine/generate.d.mts +56 -0
- package/types/engine/helpers.d.mts +348 -0
- package/types/engine/ids.d.mts +126 -0
- package/types/engine/index.d.mts +33 -0
- package/types/engine/item-docs.d.mts +73 -0
- package/types/engine/item-registry.d.mts +57 -0
- package/types/engine/journals.d.mts +121 -0
- package/types/engine/kb-manifest.d.mts +214 -0
- package/types/engine/macros.d.mts +201 -0
- package/types/engine/manifest-emit.d.mts +137 -0
- package/types/engine/map-notes.d.mts +283 -0
- package/types/engine/pack-config.d.mts +77 -0
- package/types/engine/pack-router.d.mts +53 -0
- package/types/engine/prose-config.d.mts +121 -0
- package/types/engine/prose-lint.d.mts +59 -0
- package/types/engine/region-events.d.mts +43 -0
- package/types/engine/scene-levels.d.mts +36 -0
- package/types/engine/scenes.d.mts +48 -0
- package/types/engine/site-build.d.mts +214 -0
- package/types/engine/site-index.d.mts +141 -0
- package/types/engine/web-wikilinks.d.mts +67 -0
- package/types/engine/wikilink-syntax.d.mts +101 -0
- package/types/engine/wikilinks.d.mts +172 -0
- package/types/index.d.mts +3 -0
- package/types/manifest.d.mts +52 -4
- package/types/markdownlint-config.d.mts +8 -0
- package/types/prettier-config.d.mts +1 -0
- package/types/sohl/actors.d.mts +65 -0
- package/types/sohl/affiliation-standings.d.mts +22 -0
- package/types/sohl/being-info.d.mts +78 -0
- package/types/sohl/default-item-art.d.mts +28 -0
- package/types/sohl/index.d.mts +7 -0
- package/types/sohl/item-builders.d.mts +11 -0
- package/types/sohl/item-fields.d.mts +20 -0
- package/types/sohl/items.d.mts +19 -0
- package/types/sohl/kb-passes.d.mts +71 -0
- package/types/sohl/note-schemas.d.mts +7 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a markdown body into pages by top-level H1 headings. Fenced
|
|
3
|
+
* code blocks are respected so `# foo` inside ``` blocks doesn't trigger
|
|
4
|
+
* a split. Content before the first H1 (if non-empty) becomes a leading
|
|
5
|
+
* page. Each H1 yields a page whose name is the heading
|
|
6
|
+
* text (with any `{#anchor-id}` suffix stripped out and surfaced as
|
|
7
|
+
* `anchorId`).
|
|
8
|
+
*
|
|
9
|
+
* `leadName` names that leading page. A journal note's is "Introduction",
|
|
10
|
+
* because it introduces the pages that follow. An item doc's is the item — a
|
|
11
|
+
* note with no headings at all is one page holding the whole description, and
|
|
12
|
+
* calling that page "Introduction" would label the description as a preamble to
|
|
13
|
+
* nothing.
|
|
14
|
+
*
|
|
15
|
+
* Returns an array of `{ name, anchorId, markdown }` in document order.
|
|
16
|
+
*/
|
|
17
|
+
export function splitPages(body: any, leadName?: string): {
|
|
18
|
+
name: string;
|
|
19
|
+
anchorSlug: null;
|
|
20
|
+
level: number;
|
|
21
|
+
markdown: string;
|
|
22
|
+
}[];
|
|
23
|
+
/**
|
|
24
|
+
* Two headings in one note sharing an `{#anchor}` derive the same page id, which
|
|
25
|
+
* the LevelDB packer reports only as an opaque duplicate-key collision. Catch it
|
|
26
|
+
* here, where the note and the slug can be named.
|
|
27
|
+
*
|
|
28
|
+
* @param {Array<{anchorSlug: string|null}>} rawPages - From {@link splitPages}.
|
|
29
|
+
* @param {string} noteName - The note, for the error message.
|
|
30
|
+
* @throws {Error} When an anchor is declared twice in the same note.
|
|
31
|
+
*/
|
|
32
|
+
export function assertUniqueAnchors(rawPages: Array<{
|
|
33
|
+
anchorSlug: string | null;
|
|
34
|
+
}>, noteName: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* The id of one page within its entry.
|
|
37
|
+
*
|
|
38
|
+
* An anchored page takes the id its inbound links compute from the note id and
|
|
39
|
+
* the slug, so link and page agree without shared state. Every other page is
|
|
40
|
+
* keyed by its position and name, which is what lets the items pass address an
|
|
41
|
+
* item doc's first page without having compiled it (see
|
|
42
|
+
* {@link sohl.utils.packs.itemDocPointer}).
|
|
43
|
+
*
|
|
44
|
+
* @param {string} entryId - The owning JournalEntry's `_id`.
|
|
45
|
+
* @param {{anchorSlug: string|null, name: string}} page - From {@link splitPages}.
|
|
46
|
+
* @param {number} index - The page's position in the entry.
|
|
47
|
+
* @returns {string} A 16-character Foundry id.
|
|
48
|
+
*/
|
|
49
|
+
export function journalPageId(entryId: string, page: {
|
|
50
|
+
anchorSlug: string | null;
|
|
51
|
+
name: string;
|
|
52
|
+
}, index: number): string;
|
|
53
|
+
/**
|
|
54
|
+
* Compile split pages into JournalEntryPage documents.
|
|
55
|
+
*
|
|
56
|
+
* @param {Array<object>} rawPages - From {@link splitPages}.
|
|
57
|
+
* @param {string} entryId - The owning JournalEntry's `_id`.
|
|
58
|
+
* @param {string} noteName - The note, for error messages.
|
|
59
|
+
* @returns {Array<{_id: string, name: string, type: string,
|
|
60
|
+
* title: {show: boolean, level: number},
|
|
61
|
+
* text: {format: number, content: string}, _key: string}>} The page
|
|
62
|
+
* documents, in order.
|
|
63
|
+
* @throws {Error} When the note has no content at all, or repeats an anchor.
|
|
64
|
+
*/
|
|
65
|
+
export function buildPages(rawPages: Array<object>, entryId: string, noteName: string): Array<{
|
|
66
|
+
_id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
type: string;
|
|
69
|
+
title: {
|
|
70
|
+
show: boolean;
|
|
71
|
+
level: number;
|
|
72
|
+
};
|
|
73
|
+
text: {
|
|
74
|
+
format: number;
|
|
75
|
+
content: string;
|
|
76
|
+
};
|
|
77
|
+
_key: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Assemble one JournalEntry document from a note's converted markdown.
|
|
81
|
+
*
|
|
82
|
+
* Shared with the scenes pass, which needs the *same* entry a map note's prose
|
|
83
|
+
* compiles into so it can bundle it into an Adventure alongside the Scene. Two
|
|
84
|
+
* passes deriving the same document from the same body is what keeps a map
|
|
85
|
+
* pin's `pageId` pointing at a page that actually exists.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} params
|
|
88
|
+
* @param {string} params.id - The entry's `_id`.
|
|
89
|
+
* @param {string} params.name - The entry's name.
|
|
90
|
+
* @param {string} params.markdown - The body, tables expanded and wikilinks
|
|
91
|
+
* resolved.
|
|
92
|
+
* @param {string} [params.leadName] - Name for the page before the first
|
|
93
|
+
* heading; see {@link splitPages}.
|
|
94
|
+
* @param {string|null} [params.folder] - The folder id, or `null`.
|
|
95
|
+
* @param {object} [params.flags] - Document flags.
|
|
96
|
+
* @returns {object} The JournalEntry document, keyed for the pack.
|
|
97
|
+
*/
|
|
98
|
+
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, }: {
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
markdown: string;
|
|
102
|
+
leadName?: string | undefined;
|
|
103
|
+
folder?: string | null | undefined;
|
|
104
|
+
flags?: object | undefined;
|
|
105
|
+
}): object;
|
|
106
|
+
export class Journals extends BasePackCompiler {
|
|
107
|
+
/**
|
|
108
|
+
* How many of the compiled entries were documentation for a document
|
|
109
|
+
* compiled elsewhere, for the summary.
|
|
110
|
+
*
|
|
111
|
+
* @type {number}
|
|
112
|
+
*/
|
|
113
|
+
docEntries: number;
|
|
114
|
+
/** @inheritdoc */
|
|
115
|
+
onCompiled(fm: any): void;
|
|
116
|
+
/** @inheritdoc */
|
|
117
|
+
reportCompiled(stats: any): void;
|
|
118
|
+
/** @inheritdoc */
|
|
119
|
+
reportDetail(stats: any): void;
|
|
120
|
+
}
|
|
121
|
+
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **canonical** address of a note: fully qualified, one spelling per
|
|
3
|
+
* document, and globally unique.
|
|
4
|
+
*
|
|
5
|
+
* The written form of a link may omit the package (`[[skill-lang]]`), which
|
|
6
|
+
* defaults it to the citing note's own. Everything internal — index keys,
|
|
7
|
+
* manifest keys, every lookup — uses this instead, so no consumer has to know
|
|
8
|
+
* what a short form defaulted to.
|
|
9
|
+
*
|
|
10
|
+
* Global uniqueness is what lets a foreign manifest merge straight into a local
|
|
11
|
+
* index: the keys cannot collide by accident, so a key already present on merge
|
|
12
|
+
* is a real conflict rather than an artefact of two packages sharing a
|
|
13
|
+
* namespace. `(type, shortcode)` alone is unique only *within* a package, and
|
|
14
|
+
* two independently authored packages reaching for the same short string is a
|
|
15
|
+
* matter of time (#1499).
|
|
16
|
+
*
|
|
17
|
+
* @param {string} pkg - The owning **content** package (`sohl`, `thalorna`) —
|
|
18
|
+
* not the Foundry package, which varies per compilation target.
|
|
19
|
+
* @param {string} type - The note's `type`.
|
|
20
|
+
* @param {string} shortcode - The note's `shortcode`.
|
|
21
|
+
* @returns {string} `package/type/shortcode`, lowercased.
|
|
22
|
+
*/
|
|
23
|
+
export function canonicalKey(pkg: string, type: string, shortcode: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Reads a canonical key back into its parts.
|
|
26
|
+
*
|
|
27
|
+
* Unambiguous because no package, type or shortcode contains a hyphen — types
|
|
28
|
+
* are bare words and shortcodes are `^[A-Za-z0-9]+$` (#1397).
|
|
29
|
+
*
|
|
30
|
+
* @param {string} key - A canonical key.
|
|
31
|
+
* @returns {{package: string, type: string, shortcode: string}|null} The parts,
|
|
32
|
+
* or `null` when the key is not in canonical form.
|
|
33
|
+
*/
|
|
34
|
+
export function readCanonicalKey(key: string): {
|
|
35
|
+
package: string;
|
|
36
|
+
type: string;
|
|
37
|
+
shortcode: string;
|
|
38
|
+
} | null;
|
|
39
|
+
/**
|
|
40
|
+
* The package-relative address a site-absolute URL records as.
|
|
41
|
+
*
|
|
42
|
+
* Strips the emitting package's own base, so what lands in the manifest says
|
|
43
|
+
* *where in the package* a page is and nothing about where the package itself
|
|
44
|
+
* is mounted. A URL outside the base is an error rather than a best effort: it
|
|
45
|
+
* would record an address that silently resolves to the wrong place once a
|
|
46
|
+
* consumer prefixes its own base.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} url - The site-absolute URL the emitting build publishes at.
|
|
49
|
+
* @param {string} base - That build's base for the package, e.g. `"/thalorna/"`.
|
|
50
|
+
* @returns {string} The address relative to `base`, with no leading slash.
|
|
51
|
+
*/
|
|
52
|
+
export function packageRelative(url: string, base: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* The URL a package-relative address resolves to in this build.
|
|
55
|
+
*
|
|
56
|
+
* Plain concatenation, which is what makes an absolute-origin base work: a base
|
|
57
|
+
* of `"https://thalorna.example.org/"` yields an absolute link, and one of
|
|
58
|
+
* `"/thalorna/"` a root-relative one, with no other rule to keep in step.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} rel - The package-relative address from a manifest entry.
|
|
61
|
+
* @param {string} base - This build's base for that package.
|
|
62
|
+
* @returns {string} The resolved URL.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePackageUrl(rel: string, base: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Builds one package's manifest from the KB build's own entries.
|
|
67
|
+
*
|
|
68
|
+
* Only notes carrying a `shortcode` appear: the shortcode is the stable
|
|
69
|
+
* identity another package addresses them by, and a note without one cannot be
|
|
70
|
+
* the target of a cross-package link at all.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} pkg - The package name, e.g. `"sohl"`.
|
|
73
|
+
* @param {Array<object>} entries - KB entries (`{ fm, name, url }`).
|
|
74
|
+
* @param {string} [base] - Where *this* build serves `pkg`, stripped from each
|
|
75
|
+
* entry's URL so the recorded address is package-relative (#1465). Omitted,
|
|
76
|
+
* this build publishes no web surface for the package and no entry carries a
|
|
77
|
+
* `path` — see below.
|
|
78
|
+
* @param {string} [foundryPackage] - The Foundry package this build ships the
|
|
79
|
+
* compiled documents in. Given, each entry also carries the `uuid` /
|
|
80
|
+
* `docUuid` a pack build resolves against; omitted, the manifest describes
|
|
81
|
+
* the web surface only.
|
|
82
|
+
* @returns {object} The manifest document.
|
|
83
|
+
*/
|
|
84
|
+
export function buildManifest(pkg: string, entries: Array<object>, base?: string, foundryPackage?: string): object;
|
|
85
|
+
/**
|
|
86
|
+
* Writes one manifest per package into `dir`.
|
|
87
|
+
*
|
|
88
|
+
* @param {Map<string, Array<object>>} entriesByPackage - Package → entries.
|
|
89
|
+
* @param {string} dir - Output directory; created if absent.
|
|
90
|
+
* @param {Record<string, string>} bases - Package → where *this* build serves
|
|
91
|
+
* it, which is what each entry's address is recorded relative to. This is the
|
|
92
|
+
* emitting build's own layout, not {@link PACKAGE_BASE}: a package's own site
|
|
93
|
+
* commonly serves it at `"/"` while a consumer mounts it under a prefix.
|
|
94
|
+
* @param {Record<string, string>} [foundryPackages] - Package → the Foundry
|
|
95
|
+
* package shipping its documents. Only a package this build publishes can
|
|
96
|
+
* have one, since the UUID names where *this* repository ships them.
|
|
97
|
+
* @returns {Array<{ package: string, file: string, count: number }>} What was written.
|
|
98
|
+
*/
|
|
99
|
+
export function writeManifests(entriesByPackage: Map<string, Array<object>>, dir: string, bases: Record<string, string>, foundryPackages?: Record<string, string>): Array<{
|
|
100
|
+
package: string;
|
|
101
|
+
file: string;
|
|
102
|
+
count: number;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Loads vendored manifests for packages this build does not itself publish.
|
|
106
|
+
*
|
|
107
|
+
* A package built locally is skipped even if a manifest for it is present: the
|
|
108
|
+
* live build is authoritative and a vendored copy of it can only be stale.
|
|
109
|
+
*
|
|
110
|
+
* Each entry's package-relative address is resolved against this build's base
|
|
111
|
+
* for that package (#1465), so what the index holds is a usable `url` and every
|
|
112
|
+
* consumer downstream is unchanged by the format.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} dir - Directory of vendored `<package>.json` manifests.
|
|
115
|
+
* @param {Iterable<string>} localPackages - Packages this build publishes.
|
|
116
|
+
* @param {Record<string, string>} [bases] - Package → base to resolve against;
|
|
117
|
+
* defaults to {@link PACKAGE_BASE}.
|
|
118
|
+
* @returns {{ index: Map<string, object>, packages: Set<string>, stale: Array<object> }}
|
|
119
|
+
* `index` maps the canonical `package-type-shortcode` → `{ url, name, uuid,
|
|
120
|
+
* doc, anchors, type, package }`. Keys are globally unique, so this merges
|
|
121
|
+
* directly into a local index with no prefixing and no separate lookup path.
|
|
122
|
+
* `url` is `undefined` for an entry with no page (#1516) and `uuid` for one
|
|
123
|
+
* that compiles into no document, so a caller must check the address it
|
|
124
|
+
* intends to use rather than assume a hit carries it.
|
|
125
|
+
*/
|
|
126
|
+
export function loadForeignManifests(dir: string, localPackages: Iterable<string>, bases?: Record<string, string>): {
|
|
127
|
+
index: Map<string, object>;
|
|
128
|
+
packages: Set<string>;
|
|
129
|
+
stale: Array<object>;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Whether every linkable package is accounted for, locally or by manifest.
|
|
133
|
+
*
|
|
134
|
+
* This is what gates the dead-link guard. It is deliberately derived from data
|
|
135
|
+
* rather than set by a flag: the guard turns itself on the moment the last
|
|
136
|
+
* missing manifest appears, instead of waiting for someone to remember.
|
|
137
|
+
*
|
|
138
|
+
* @param {Iterable<string>} localPackages - Packages this build publishes.
|
|
139
|
+
* @param {Iterable<string>} manifestPackages - Packages loaded from manifests.
|
|
140
|
+
* @returns {{ complete: boolean, missing: Array<string> }}
|
|
141
|
+
*/
|
|
142
|
+
export function manifestsComplete(localPackages: Iterable<string>, manifestPackages: Iterable<string>): {
|
|
143
|
+
complete: boolean;
|
|
144
|
+
missing: Array<string>;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Packages that publish a manifest and therefore exchange addresses.
|
|
148
|
+
*
|
|
149
|
+
* The guard in {@link manifestsComplete} stays off until every one of these is
|
|
150
|
+
* accounted for, so adding a package here without also publishing its manifest
|
|
151
|
+
* relaxes the build rather than breaking it.
|
|
152
|
+
*/
|
|
153
|
+
export const LINK_PACKAGES: readonly string[];
|
|
154
|
+
/**
|
|
155
|
+
* Manifest format version.
|
|
156
|
+
*
|
|
157
|
+
* Bumped to 2 by #1465: entries changed from a site-absolute `url` to a
|
|
158
|
+
* package-relative `path`. The two shapes are indistinguishable to a naive
|
|
159
|
+
* reader — prefixing a v1 `url` yields `/thalorna/thalorna/…`, which resolves,
|
|
160
|
+
* renders, and 404s — so the version is what makes a stale vendored file an
|
|
161
|
+
* error rather than a wrong link.
|
|
162
|
+
*
|
|
163
|
+
* Bumped to 4 by #1499: keys use the authored hyphen separator
|
|
164
|
+
* (`sohl-affliction-aconite`) so a key *is* the address an author writes; an
|
|
165
|
+
* item's documentation became an entry in its own right
|
|
166
|
+
* (`sohl-docaffliction-aconite`) rather than a second field; and entries gained
|
|
167
|
+
* `anchors`, mapping a note's named sections to the full UUID each compiled to.
|
|
168
|
+
*
|
|
169
|
+
* Bumped to 3 by #1499: keys became **canonical** — fully qualified
|
|
170
|
+
* `package/type/shortcode` rather than `type/shortcode` — and entries gained the
|
|
171
|
+
* Foundry `uuid` / `docUuid` beside the web `path`. A v2 key read as a v3 one
|
|
172
|
+
* addresses a package named after a type, so again the version is what turns a
|
|
173
|
+
* stale vendored file into an error.
|
|
174
|
+
*
|
|
175
|
+
* Bumped to 5 by #1516: `path` became optional, so a package that ships
|
|
176
|
+
* compendiums and publishes no site can still publish the Foundry addresses of
|
|
177
|
+
* its documents — the mirror of an entry that has a `path` and no `uuid`.
|
|
178
|
+
*/
|
|
179
|
+
export const MANIFEST_VERSION: 5;
|
|
180
|
+
/**
|
|
181
|
+
* Every version this build can read, newest last.
|
|
182
|
+
*
|
|
183
|
+
* A version exists to stop a file whose values *read differently* from being
|
|
184
|
+
* resolved anyway, and that is the only thing it is allowed to gate. Every bump
|
|
185
|
+
* so far did change a reading — a v2 key read as a v4 one addresses a package
|
|
186
|
+
* named after a type — so each dropped its predecessors. **v5 did not**: it
|
|
187
|
+
* only permits an absent `path`, so every v4 value still means exactly what it
|
|
188
|
+
* meant, and refusing v4 would make a purely relaxing change a flag day in
|
|
189
|
+
* which every package must re-emit on the same afternoon or every build breaks
|
|
190
|
+
* (#1516).
|
|
191
|
+
*
|
|
192
|
+
* The unsafe direction is unchanged and still hard-fails: an older consumer
|
|
193
|
+
* meeting a newer file rejects it, because it cannot know what the new shape
|
|
194
|
+
* permits. Widening is therefore always safe to do here first and adopt
|
|
195
|
+
* elsewhere later.
|
|
196
|
+
*/
|
|
197
|
+
export const READABLE_VERSIONS: readonly number[];
|
|
198
|
+
/**
|
|
199
|
+
* Where this build serves each package, keyed by package name.
|
|
200
|
+
*
|
|
201
|
+
* One line per package, and the only edit a relocation requires: point a
|
|
202
|
+
* package at another path (`"/setting/thalorna/"`) or another origin
|
|
203
|
+
* (`"https://thalorna.example.org/"`) and every inbound link into it follows.
|
|
204
|
+
* A base is a prefix, so it must end in `/`.
|
|
205
|
+
*
|
|
206
|
+
* Only *foreign* packages are consulted — a package this build publishes is
|
|
207
|
+
* authoritative in its own entries and never resolves through a manifest — but
|
|
208
|
+
* every linkable package is listed, because which are foreign depends on the
|
|
209
|
+
* consuming repository and this file is vendored into each of them.
|
|
210
|
+
*/
|
|
211
|
+
export const PACKAGE_BASE: Readonly<{
|
|
212
|
+
sohl: "/sohl/";
|
|
213
|
+
thalorna: "/thalorna/";
|
|
214
|
+
}>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body of the first **language-tagged** JavaScript fence in a markdown
|
|
3
|
+
* block, verbatim.
|
|
4
|
+
*
|
|
5
|
+
* "Language-tagged" is the whole rule: an untagged fence is a code sample
|
|
6
|
+
* whose language nobody stated, and treating it as the macro's source would
|
|
7
|
+
* make an author's illustrative snippet executable. A fence tagged for another
|
|
8
|
+
* language is skipped for the same reason.
|
|
9
|
+
*
|
|
10
|
+
* The opening delimiter may be longer than three backticks, so a script may
|
|
11
|
+
* itself contain a fence; the closing delimiter must be at least as long, as
|
|
12
|
+
* CommonMark requires.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} markdown - The markdown to search.
|
|
15
|
+
* @returns {string|null} The fence's contents, with no trailing newline, or
|
|
16
|
+
* `null` when the block holds no tagged JS fence.
|
|
17
|
+
*/
|
|
18
|
+
export function extractJsFence(markdown: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* The `command` a macro note compiles to: the first tagged JS fence on its
|
|
21
|
+
* `{#script}` page.
|
|
22
|
+
*
|
|
23
|
+
* Read from the **raw** note body, before tables are expanded and wikilinks
|
|
24
|
+
* converted, so the executable copy is exactly what the author wrote even
|
|
25
|
+
* where the journal's rendered copy of the same fence is not.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} body - The note's markdown body, frontmatter stripped.
|
|
28
|
+
* @param {string} name - The macro's name, for the error messages.
|
|
29
|
+
* @returns {string} The macro's command.
|
|
30
|
+
* @throws {Error} When the note declares no `{#script}` page, or that page
|
|
31
|
+
* holds no language-tagged JS fence. Either is a build error: a macro with
|
|
32
|
+
* no command is a macro-bar button that does nothing.
|
|
33
|
+
*/
|
|
34
|
+
export function macroCommand(body: string, name: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The **Foundry** macro type a note compiles to — not the note's `type:`,
|
|
37
|
+
* which stays `macro` because that is what routes it to this pack.
|
|
38
|
+
*
|
|
39
|
+
* Foundry's schema initialises `type` to `CHAT`, so a script macro has to say
|
|
40
|
+
* so explicitly; this states it for every note and defaults the authored field
|
|
41
|
+
* to `script`, which is the only kind that compiles.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} fm - The note's frontmatter.
|
|
44
|
+
* @param {string} label - The macro, for the error message.
|
|
45
|
+
* @returns {"script"} The macro type.
|
|
46
|
+
* @throws {Error} For `chat`, and for any value Foundry does not define.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveMacroType(fm: object, label: string): "script";
|
|
49
|
+
/**
|
|
50
|
+
* The Foundry macro scope a note compiles to.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} fm - The note's frontmatter.
|
|
53
|
+
* @param {string} label - The macro, for the error message.
|
|
54
|
+
* @returns {string} One of {@link MACRO_SCOPES}.
|
|
55
|
+
* @throws {Error} When the authored scope is not one Foundry defines — it
|
|
56
|
+
* would fail the schema's `choices` validation at load and be replaced by
|
|
57
|
+
* the default, shipping a macro whose authored reach had quietly widened.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveMacroScope(fm: object, label: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* A compiled Macro document, in the shape the LevelDB packer consumes.
|
|
62
|
+
*
|
|
63
|
+
* @typedef {object} MacroDocument
|
|
64
|
+
* @property {string} name - The macro's display name.
|
|
65
|
+
* @property {string} type - The Foundry macro type; always `script`.
|
|
66
|
+
* @property {null} author - No authoring user; Foundry's field is nullable.
|
|
67
|
+
* @property {string} img - The Foundry-relative artwork path.
|
|
68
|
+
* @property {string} scope - One of {@link MACRO_SCOPES}.
|
|
69
|
+
* @property {string} command - The script the macro runs.
|
|
70
|
+
* @property {string|null} folder - The folder id, or `null` for the root.
|
|
71
|
+
* @property {number} sort - Sort order within its folder.
|
|
72
|
+
* @property {{default: number}} ownership - Default ownership level.
|
|
73
|
+
* @property {object} flags - Document flags from frontmatter.
|
|
74
|
+
* @property {string} _id - The Foundry document id.
|
|
75
|
+
* @property {object} _stats - The `_stats` block.
|
|
76
|
+
* @property {string} _key - The LevelDB key, `!macros!<id>`.
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* The compendium envelope for one Macro.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} fm - The note's frontmatter.
|
|
82
|
+
* @param {object} opts
|
|
83
|
+
* @param {string} opts.command - The command, from {@link macroCommand}.
|
|
84
|
+
* @param {string|null} [opts.folder] - The resolved folder id.
|
|
85
|
+
* @param {object} [opts.stats] - The `_stats` block.
|
|
86
|
+
* @returns {MacroDocument} The Macro document.
|
|
87
|
+
* @throws {Error} When the frontmatter's macro type or scope is unusable.
|
|
88
|
+
*/
|
|
89
|
+
export function buildMacroEntry(fm: object, { command, folder, stats }: {
|
|
90
|
+
command: string;
|
|
91
|
+
folder?: string | null | undefined;
|
|
92
|
+
stats?: object | undefined;
|
|
93
|
+
}): MacroDocument;
|
|
94
|
+
/**
|
|
95
|
+
* The anchor the executable script lives under: `# Script {#script}`.
|
|
96
|
+
*
|
|
97
|
+
* A reserved slug rather than a heading name, because the heading is prose an
|
|
98
|
+
* author may word freely ("The Script", "Source") while the address must be
|
|
99
|
+
* stable — it is what the compiler looks for and what an inbound section link
|
|
100
|
+
* spells.
|
|
101
|
+
*/
|
|
102
|
+
export const MACRO_SCRIPT_ANCHOR: "script";
|
|
103
|
+
/**
|
|
104
|
+
* The Foundry macro types (`CONST.MACRO_TYPES`).
|
|
105
|
+
*
|
|
106
|
+
* Only `script` compiles. `chat` is a real Foundry type but a different
|
|
107
|
+
* document altogether — its `command` is chat text, not source, so none of the
|
|
108
|
+
* fence rules above apply to it — and half-implementing it would ship a macro
|
|
109
|
+
* whose body was a code block posted verbatim into chat.
|
|
110
|
+
*/
|
|
111
|
+
export const MACRO_TYPES: readonly string[];
|
|
112
|
+
/** The Foundry macro scopes (`CONST.MACRO_SCOPES`), in schema order. */
|
|
113
|
+
export const MACRO_SCOPES: readonly string[];
|
|
114
|
+
/**
|
|
115
|
+
* Foundry's own default macro artwork, used when a note authors no `img`.
|
|
116
|
+
*
|
|
117
|
+
* A core path, deliberately: it is not translated by {@link resolveImg} (which
|
|
118
|
+
* roots `icons/…` under this system's assets), so it must be stated after that
|
|
119
|
+
* translation rather than as authored frontmatter.
|
|
120
|
+
*/
|
|
121
|
+
export const DEFAULT_MACRO_IMG: "icons/svg/dice-target.svg";
|
|
122
|
+
/**
|
|
123
|
+
* Macros pack compiler.
|
|
124
|
+
*
|
|
125
|
+
* Walks the content tree and compiles every `package: sohl`, `type: macro`
|
|
126
|
+
* note into one Macro document. The same note's documentation is compiled by
|
|
127
|
+
* the journals pass; neither pass reads the other's output.
|
|
128
|
+
*/
|
|
129
|
+
export class Macros extends BasePackCompiler {
|
|
130
|
+
/**
|
|
131
|
+
* Compile one note into a Macro.
|
|
132
|
+
*
|
|
133
|
+
* @param {object} fm - The note's frontmatter.
|
|
134
|
+
* @param {string} body - The note body, frontmatter stripped and otherwise
|
|
135
|
+
* exactly as authored.
|
|
136
|
+
* @returns {MacroDocument} The Macro document.
|
|
137
|
+
*/
|
|
138
|
+
buildEntry(fm: object, body: string): MacroDocument;
|
|
139
|
+
/** @inheritdoc */
|
|
140
|
+
reportDetail(stats: any): void;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A compiled Macro document, in the shape the LevelDB packer consumes.
|
|
144
|
+
*/
|
|
145
|
+
export type MacroDocument = {
|
|
146
|
+
/**
|
|
147
|
+
* - The macro's display name.
|
|
148
|
+
*/
|
|
149
|
+
name: string;
|
|
150
|
+
/**
|
|
151
|
+
* - The Foundry macro type; always `script`.
|
|
152
|
+
*/
|
|
153
|
+
type: string;
|
|
154
|
+
/**
|
|
155
|
+
* - No authoring user; Foundry's field is nullable.
|
|
156
|
+
*/
|
|
157
|
+
author: null;
|
|
158
|
+
/**
|
|
159
|
+
* - The Foundry-relative artwork path.
|
|
160
|
+
*/
|
|
161
|
+
img: string;
|
|
162
|
+
/**
|
|
163
|
+
* - One of {@link MACRO_SCOPES}.
|
|
164
|
+
*/
|
|
165
|
+
scope: string;
|
|
166
|
+
/**
|
|
167
|
+
* - The script the macro runs.
|
|
168
|
+
*/
|
|
169
|
+
command: string;
|
|
170
|
+
/**
|
|
171
|
+
* - The folder id, or `null` for the root.
|
|
172
|
+
*/
|
|
173
|
+
folder: string | null;
|
|
174
|
+
/**
|
|
175
|
+
* - Sort order within its folder.
|
|
176
|
+
*/
|
|
177
|
+
sort: number;
|
|
178
|
+
/**
|
|
179
|
+
* - Default ownership level.
|
|
180
|
+
*/
|
|
181
|
+
ownership: {
|
|
182
|
+
default: number;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* - Document flags from frontmatter.
|
|
186
|
+
*/
|
|
187
|
+
flags: object;
|
|
188
|
+
/**
|
|
189
|
+
* - The Foundry document id.
|
|
190
|
+
*/
|
|
191
|
+
_id: string;
|
|
192
|
+
/**
|
|
193
|
+
* - The `_stats` block.
|
|
194
|
+
*/
|
|
195
|
+
_stats: object;
|
|
196
|
+
/**
|
|
197
|
+
* - The LevelDB key, `!macros!<id>`.
|
|
198
|
+
*/
|
|
199
|
+
_key: string;
|
|
200
|
+
};
|
|
201
|
+
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every page of a note's journal, as `anchorName → whole UUID`.
|
|
3
|
+
*
|
|
4
|
+
* Whole, not a fragment appended to the entry's UUID: nothing owns a page
|
|
5
|
+
* address, so a complete link restates no fact, and it keeps the page-id hash
|
|
6
|
+
* out of the published contract entirely — a consumer resolves
|
|
7
|
+
* `[[docaffliction-aconite#crafting]]` with a lookup instead of reimplementing
|
|
8
|
+
* a sha256/base64/truncate rule.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} entryUuid - The journal entry's UUID.
|
|
11
|
+
* @param {string} entryId - The entry's id, which page ids hash against.
|
|
12
|
+
* @param {string} body - The note's markdown body.
|
|
13
|
+
* @param {string} name - The note's name, used as the lead page's title.
|
|
14
|
+
* @returns {Record<string, string>} The anchors.
|
|
15
|
+
*/
|
|
16
|
+
export function anchorsOf(entryUuid: string, entryId: string, body: string, name: string): Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* The manifest entries a single note produces.
|
|
19
|
+
*
|
|
20
|
+
* An item note produces **two**: the item, and separately the JournalEntry its
|
|
21
|
+
* prose compiles into. They are two documents with two UUIDs, so they get two
|
|
22
|
+
* addresses; the item's entry points at the other by address rather than
|
|
23
|
+
* repeating its UUID, because the doc entry owns that fact (#1499). A `macro`
|
|
24
|
+
* note is the same arrangement (#1514), which is why the type set comes from
|
|
25
|
+
* {@link hasDocEntry} rather than being spelled here — the journals compiler
|
|
26
|
+
* reads the same one, so a manifest cannot claim documentation nothing compiled.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} fm - Parsed frontmatter.
|
|
29
|
+
* @param {string} name - The note's display name.
|
|
30
|
+
* @param {string} address - The note's package-relative address.
|
|
31
|
+
* @param {string} body - The note's markdown body.
|
|
32
|
+
* @param {object} ctx - Resolved identities: `{ contentPackage,
|
|
33
|
+
* foundryPackageId, packRouter }`.
|
|
34
|
+
* @returns {Array<object>} One or two entries, in {@link buildManifest}'s shape.
|
|
35
|
+
*/
|
|
36
|
+
export function entriesForNote(fm: object, name: string, address: string, body: string, ctx: object): Array<object>;
|
|
37
|
+
/**
|
|
38
|
+
* Every note this package publishes, as manifest entries.
|
|
39
|
+
*
|
|
40
|
+
* Drafts are excluded because the site does not publish them, and an entry for
|
|
41
|
+
* an unpublished page is exactly the dead link the manifest exists to prevent.
|
|
42
|
+
* A note belonging to another content package is skipped for the same reason in
|
|
43
|
+
* reverse: this build is not authoritative for it, and its own build says where
|
|
44
|
+
* it lives.
|
|
45
|
+
*
|
|
46
|
+
* A note that has no address is **reported, not guessed** — the finding carries
|
|
47
|
+
* the file and the reason, so a caller can print it or fail on it. Inventing an
|
|
48
|
+
* address would put an entry in the manifest asserting a page that does not
|
|
49
|
+
* exist.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} contentBase - Absolute path to the content tree.
|
|
52
|
+
* @param {object} ctx - `{ contentPackage, foundryPackageId, packRouter,
|
|
53
|
+
* scheme }`.
|
|
54
|
+
* @returns {{entries: Array<object>, notes: number,
|
|
55
|
+
* skipped: Array<{file: string, reason: string}>}}
|
|
56
|
+
*/
|
|
57
|
+
export function collectManifestEntries(contentBase: string, ctx: object): {
|
|
58
|
+
entries: Array<object>;
|
|
59
|
+
notes: number;
|
|
60
|
+
skipped: Array<{
|
|
61
|
+
file: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
}>;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The identities and scheme an emission runs against, from configuration.
|
|
67
|
+
*
|
|
68
|
+
* Resolved in one place and passed down, rather than read at each use, so the
|
|
69
|
+
* pass itself is a pure function of its context and a test can drive it without
|
|
70
|
+
* standing up a configuration.
|
|
71
|
+
*
|
|
72
|
+
* @param {object} [config] - A resolved configuration; loaded when omitted.
|
|
73
|
+
* @returns {{contentPackage: string, foundryPackageId: string, packRouter: object,
|
|
74
|
+
* scheme: {prefix: string, landing: string}, web: boolean,
|
|
75
|
+
* skipDirectories: readonly string[]}}
|
|
76
|
+
*/
|
|
77
|
+
export function manifestContext(config?: object): {
|
|
78
|
+
contentPackage: string;
|
|
79
|
+
foundryPackageId: string;
|
|
80
|
+
packRouter: object;
|
|
81
|
+
scheme: {
|
|
82
|
+
prefix: string;
|
|
83
|
+
landing: string;
|
|
84
|
+
};
|
|
85
|
+
web: boolean;
|
|
86
|
+
skipDirectories: readonly string[];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Emits this package's link manifest.
|
|
90
|
+
*
|
|
91
|
+
* One package, because a configuration declares exactly one `contentPackage`
|
|
92
|
+
* and nothing in the surface can express a second. {@link writeManifests} keeps
|
|
93
|
+
* its package→entries map — it is the general writer — but there is no setting
|
|
94
|
+
* here to choose with.
|
|
95
|
+
*
|
|
96
|
+
* @param {object} [options] - Options.
|
|
97
|
+
* @param {string} [options.contentBase] - The content tree; defaults to the
|
|
98
|
+
* configured `paths.content`.
|
|
99
|
+
* @param {string} [options.outDir] - Where to write; defaults to the configured
|
|
100
|
+
* `paths.manifestOut`.
|
|
101
|
+
* @param {object} [options.config] - A resolved configuration; loaded when
|
|
102
|
+
* omitted.
|
|
103
|
+
* @returns {{written: Array<{package: string, file: string, count: number}>,
|
|
104
|
+
* entries: number, notes: number,
|
|
105
|
+
* skipped: Array<{file: string, reason: string}>}}
|
|
106
|
+
* @throws {Error} When the repository does not declare that it publishes a
|
|
107
|
+
* manifest, when the tree is absent, or when it yields no published note — a
|
|
108
|
+
* manifest claiming this package publishes nothing is worse than none, since
|
|
109
|
+
* a consumer reads it as authoritative and turns every link into this package
|
|
110
|
+
* into a reported typo.
|
|
111
|
+
*/
|
|
112
|
+
export function emitLinkManifest({ contentBase, outDir, config }?: {
|
|
113
|
+
contentBase?: string | undefined;
|
|
114
|
+
outDir?: string | undefined;
|
|
115
|
+
config?: object | undefined;
|
|
116
|
+
}): {
|
|
117
|
+
written: Array<{
|
|
118
|
+
package: string;
|
|
119
|
+
file: string;
|
|
120
|
+
count: number;
|
|
121
|
+
}>;
|
|
122
|
+
entries: number;
|
|
123
|
+
notes: number;
|
|
124
|
+
skipped: Array<{
|
|
125
|
+
file: string;
|
|
126
|
+
reason: string;
|
|
127
|
+
}>;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* The reserved anchor name for a journal's **first** page.
|
|
131
|
+
*
|
|
132
|
+
* Every journal has one and it is what an item's `docHtml` points at, but it
|
|
133
|
+
* carries no authored `{#slug}` — so without a reserved name the one page that
|
|
134
|
+
* always exists would be the one page the manifest could not address. It cannot
|
|
135
|
+
* collide with an authored slug, which is `[a-z0-9-]+`.
|
|
136
|
+
*/
|
|
137
|
+
export const LEAD_ANCHOR: "$lead";
|