@heroiclands/package-build 0.6.1 → 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.
Files changed (127) hide show
  1. package/CHANGELOG-content-build.md +965 -0
  2. package/CHANGELOG.md +58 -0
  3. package/CONTENT.md +824 -0
  4. package/MIGRATING.md +127 -0
  5. package/README.md +29 -17
  6. package/bin/content-build.mjs +1082 -0
  7. package/bin/package-build.mjs +2 -2
  8. package/bin/report.mjs +2 -2
  9. package/config.mjs +21 -18
  10. package/content-config.mjs +1424 -0
  11. package/coverage.mjs +1 -1
  12. package/e2e.mjs +1 -1
  13. package/engine/abbreviations.mjs +0 -0
  14. package/engine/base-compiler.mjs +608 -0
  15. package/engine/code-fences.mjs +311 -0
  16. package/engine/compendiums.mjs +353 -0
  17. package/engine/content-address.mjs +141 -0
  18. package/engine/content-links.mjs +438 -0
  19. package/engine/content-lint.mjs +204 -0
  20. package/engine/content-package.mjs +67 -0
  21. package/engine/content-slug.mjs +169 -0
  22. package/engine/content-tables.mjs +1216 -0
  23. package/engine/content-tree.mjs +56 -0
  24. package/engine/diagnostics.mjs +270 -0
  25. package/engine/field-reference.mjs +242 -0
  26. package/engine/field-spec.mjs +218 -0
  27. package/engine/foreign-catalog.mjs +450 -0
  28. package/engine/foreign-manifests.mjs +129 -0
  29. package/engine/frontmatter-lint.mjs +343 -0
  30. package/engine/frontmatter.mjs +258 -0
  31. package/engine/generate.mjs +305 -0
  32. package/engine/helpers.mjs +809 -0
  33. package/engine/ids.mjs +192 -0
  34. package/engine/index.mjs +142 -0
  35. package/engine/item-docs.mjs +153 -0
  36. package/engine/item-registry.mjs +129 -0
  37. package/engine/journals.mjs +374 -0
  38. package/engine/kb-manifest.mjs +473 -0
  39. package/engine/macros.mjs +333 -0
  40. package/engine/manifest-emit.mjs +339 -0
  41. package/engine/map-notes.mjs +1302 -0
  42. package/engine/pack-config.mjs +479 -0
  43. package/engine/pack-router.mjs +254 -0
  44. package/engine/prose-config.mjs +238 -0
  45. package/engine/prose-lint.mjs +294 -0
  46. package/engine/region-events.mjs +79 -0
  47. package/engine/scene-levels.mjs +420 -0
  48. package/engine/scenes.mjs +528 -0
  49. package/engine/site-build.mjs +778 -0
  50. package/engine/site-index.mjs +332 -0
  51. package/engine/web-wikilinks.mjs +330 -0
  52. package/engine/wikilink-syntax.mjs +105 -0
  53. package/engine/wikilinks.mjs +645 -0
  54. package/index.mjs +42 -25
  55. package/lang.mjs +1 -1
  56. package/manifest.mjs +79 -5
  57. package/markdownlint-config.mjs +45 -0
  58. package/package.json +78 -35
  59. package/prettier-config.mjs +34 -0
  60. package/sohl/actors.mjs +581 -0
  61. package/sohl/affiliation-standings.mjs +44 -0
  62. package/sohl/being-info.mjs +173 -0
  63. package/sohl/default-item-art.mjs +82 -0
  64. package/sohl/index.mjs +55 -0
  65. package/sohl/item-builders.mjs +114 -0
  66. package/sohl/item-fields.mjs +867 -0
  67. package/sohl/items.mjs +197 -0
  68. package/sohl/kb-passes.mjs +207 -0
  69. package/sohl/note-schemas.mjs +322 -0
  70. package/templates.mjs +1 -1
  71. package/types/config.d.mts +1 -1
  72. package/types/content-config.d.mts +571 -0
  73. package/types/engine/abbreviations.d.mts +44 -0
  74. package/types/engine/base-compiler.d.mts +320 -0
  75. package/types/engine/code-fences.d.mts +102 -0
  76. package/types/engine/compendiums.d.mts +77 -0
  77. package/types/engine/content-address.d.mts +61 -0
  78. package/types/engine/content-links.d.mts +87 -0
  79. package/types/engine/content-lint.d.mts +47 -0
  80. package/types/engine/content-package.d.mts +36 -0
  81. package/types/engine/content-slug.d.mts +59 -0
  82. package/types/engine/content-tables.d.mts +131 -0
  83. package/types/engine/content-tree.d.mts +12 -0
  84. package/types/engine/diagnostics.d.mts +152 -0
  85. package/types/engine/field-reference.d.mts +20 -0
  86. package/types/engine/field-spec.d.mts +181 -0
  87. package/types/engine/foreign-catalog.d.mts +103 -0
  88. package/types/engine/foreign-manifests.d.mts +43 -0
  89. package/types/engine/frontmatter-lint.d.mts +74 -0
  90. package/types/engine/frontmatter.d.mts +99 -0
  91. package/types/engine/generate.d.mts +56 -0
  92. package/types/engine/helpers.d.mts +348 -0
  93. package/types/engine/ids.d.mts +126 -0
  94. package/types/engine/index.d.mts +33 -0
  95. package/types/engine/item-docs.d.mts +73 -0
  96. package/types/engine/item-registry.d.mts +57 -0
  97. package/types/engine/journals.d.mts +121 -0
  98. package/types/engine/kb-manifest.d.mts +214 -0
  99. package/types/engine/macros.d.mts +201 -0
  100. package/types/engine/manifest-emit.d.mts +137 -0
  101. package/types/engine/map-notes.d.mts +283 -0
  102. package/types/engine/pack-config.d.mts +77 -0
  103. package/types/engine/pack-router.d.mts +53 -0
  104. package/types/engine/prose-config.d.mts +121 -0
  105. package/types/engine/prose-lint.d.mts +59 -0
  106. package/types/engine/region-events.d.mts +43 -0
  107. package/types/engine/scene-levels.d.mts +36 -0
  108. package/types/engine/scenes.d.mts +48 -0
  109. package/types/engine/site-build.d.mts +214 -0
  110. package/types/engine/site-index.d.mts +141 -0
  111. package/types/engine/web-wikilinks.d.mts +67 -0
  112. package/types/engine/wikilink-syntax.d.mts +101 -0
  113. package/types/engine/wikilinks.d.mts +172 -0
  114. package/types/index.d.mts +3 -0
  115. package/types/manifest.d.mts +52 -4
  116. package/types/markdownlint-config.d.mts +8 -0
  117. package/types/prettier-config.d.mts +1 -0
  118. package/types/sohl/actors.d.mts +65 -0
  119. package/types/sohl/affiliation-standings.d.mts +22 -0
  120. package/types/sohl/being-info.d.mts +78 -0
  121. package/types/sohl/default-item-art.d.mts +28 -0
  122. package/types/sohl/index.d.mts +7 -0
  123. package/types/sohl/item-builders.d.mts +11 -0
  124. package/types/sohl/item-fields.d.mts +20 -0
  125. package/types/sohl/items.d.mts +19 -0
  126. package/types/sohl/kb-passes.d.mts +71 -0
  127. 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";