@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.
Files changed (127) hide show
  1. package/CHANGELOG-content-build.md +965 -0
  2. package/CHANGELOG.md +95 -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 -34
  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,48 @@
1
+ /**
2
+ * Every SoHL action name this build knows about, for the `action:` warning on a
3
+ * region trigger.
4
+ *
5
+ * Deliberately a **superset**, gathered from the localization keys every
6
+ * intrinsic action's `title:` points at and from the `shortcode:` / `executor:`
7
+ * string literals the action definitions carry. A warning that fires on a real
8
+ * action would be worse than one that misses a typo, so the wider net is the
9
+ * right one: this only has to recognise the names that exist, not enumerate
10
+ * them exactly.
11
+ *
12
+ * @param {string} repoRoot - The repository root.
13
+ * @returns {Set<string>} The known action names.
14
+ */
15
+ export function collectKnownActionNames(repoRoot: string): Set<string>;
16
+ export class Scenes extends BasePackCompiler {
17
+ constructor({ contentBase, dest, companionDests, folderResolver, repoRoot, }: {
18
+ contentBase: any;
19
+ dest: any;
20
+ companionDests?: {} | undefined;
21
+ folderResolver?: (() => null) | undefined;
22
+ repoRoot?: string | undefined;
23
+ });
24
+ /** @type {string} */
25
+ adventureDir: string;
26
+ /**
27
+ * Adventures this pass bundled, for the summary.
28
+ *
29
+ * @type {number}
30
+ */
31
+ adventureCount: number;
32
+ index: Map<string, object> | undefined;
33
+ effectsByAddress: Map<string, object> | undefined;
34
+ knownActions: Set<string> | undefined;
35
+ /** place key → `{name, img, scenes: [], journal: []}` */
36
+ places: Map<any, any> | undefined;
37
+ /**
38
+ * Bundle each pinned place into an Adventure, once every map of it has
39
+ * compiled.
40
+ *
41
+ * @returns {Promise<void>}
42
+ */
43
+ finish(): Promise<void>;
44
+ /** @inheritdoc */
45
+ reportCompiled(stats: any): void;
46
+ #private;
47
+ }
48
+ import { BasePackCompiler } from "./base-compiler.mjs";
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Every `.md` file under `dir`, depth-first in directory order.
3
+ *
4
+ * Deliberately *not* {@link walkMarkdownTree}, whose stack-based walk yields a
5
+ * tree in reverse. Order is load-bearing here and nowhere else: the address
6
+ * index resolves a bare `[[Name]]` on a first-writer-wins basis, so reversing
7
+ * the walk silently changes which page an ambiguous name resolves to. A pack
8
+ * compile has no such dependency, which is why the two walks can differ.
9
+ *
10
+ * @param {string} dir - Directory to walk.
11
+ * @param {readonly string[]} skip - Directory names to ignore at any depth.
12
+ * @returns {string[]} Absolute paths.
13
+ */
14
+ export function walkSiteTree(dir: string, skip?: readonly string[]): string[];
15
+ /**
16
+ * The content tree's pages, and what could not be addressed.
17
+ *
18
+ * @param {string} contentBase - Absolute path to the content tree.
19
+ * @param {object} ctx - `{ packages, skipDirectories, mount, scheme }`.
20
+ * @returns {{pages: object[], slugFindings: object[], fmLinkFindings: object[]}}
21
+ */
22
+ export function collectContentPages(contentBase: string, ctx: object): {
23
+ pages: object[];
24
+ slugFindings: object[];
25
+ fmLinkFindings: object[];
26
+ };
27
+ /**
28
+ * An extra tree's pages — a documentation tree published alongside the content.
29
+ *
30
+ * These preserve their **source layout** below the section rather than being
31
+ * addressed by type and slug: they are a book with chapters, and a reader
32
+ * follows their paths. A `README` is its directory's landing.
33
+ *
34
+ * @param {object} tree - `{ from, rel, section, route }`.
35
+ * @param {object} ctx - `{ mount }`.
36
+ * @returns {{pages: object[], fmLinkFindings: object[]}}
37
+ */
38
+ export function collectTreePages(tree: object, ctx: object): {
39
+ pages: object[];
40
+ fmLinkFindings: object[];
41
+ };
42
+ /**
43
+ * The integrity gates a site build runs before it writes anything.
44
+ *
45
+ * Every one of these was an inline `process.exit` in both consumer scripts, so
46
+ * none of them had a test. They are grouped here, reporting rather than exiting,
47
+ * because the order matters and the reasons are worth stating once:
48
+ *
49
+ * - **Frontmatter wikilinks** first, because frontmatter is copied to the page
50
+ * verbatim and a link written in one reaches the reader as literal `[[…]]`.
51
+ * - **Slugs and collisions** next: a note that derives no URL, or two that
52
+ * derive the same one, would silently drop or overwrite a page.
53
+ * - **Foreign manifests** last, in two steps. *Unusable* is a file this build
54
+ * cannot read; *unaddressable* is one it can read but cannot look anything up
55
+ * in — a distinction worth keeping, because the second surfaces as a pile of
56
+ * dead links blaming the notes that cite them rather than the file at fault.
57
+ *
58
+ * @param {object[]} pages - Every page, from both walks.
59
+ * @param {object} findings - `{ slugFindings, fmLinkFindings }` from collection.
60
+ * @param {object} options - `{ manifestDir }`.
61
+ * @returns {object} The gate results and, when they pass, the built index.
62
+ */
63
+ export function siteGates(pages: object[], findings: object, { manifestDir }: object): object;
64
+ /** Whether any gate produced a finding. */
65
+ export function gatesFailed(gates: any): boolean;
66
+ /**
67
+ * The universe a generated table searches, grouped by package.
68
+ *
69
+ * Reference pages only, and grouped so a page never tabulates another package's
70
+ * content: a table is a claim about what this package ships.
71
+ *
72
+ * @param {object[]} pages - Every page.
73
+ * @returns {Map<string, object[]>} Package → the notes it may tabulate.
74
+ */
75
+ export function tableUniverse(pages: object[]): Map<string, object[]>;
76
+ /**
77
+ * The frontmatter a page publishes with.
78
+ *
79
+ * An authored `aliases` is Obsidian's — a list of *names* a reader might call
80
+ * the note, which is vault addressing and stays in the vault. Hugo reads
81
+ * `aliases` as **URL redirects**, so passing them through would publish a
82
+ * redirect stub at each name. They are dropped, and this build emits no
83
+ * redirects of its own.
84
+ *
85
+ * @param {object} page - The page.
86
+ * @param {object} options - `{ sections, readmeSections, decorate }`.
87
+ * @returns {object} The frontmatter to write.
88
+ */
89
+ export function pageFrontmatter(page: object, { readmeSections, decorate }: object): object;
90
+ /** Where a page is written, relative to the output root. */
91
+ export function pageDestination(page: any): string;
92
+ /**
93
+ * Renders and writes every page.
94
+ *
95
+ * The order inside a page is load-bearing and is the same order the pack
96
+ * compilers use:
97
+ *
98
+ * 1. **Tables expand first**, and outside code-fence protection. A table is
99
+ * authored as a fenced `dataview` block, which `protectCode` would otherwise
100
+ * stash away before the expander saw it. Expanding first leaves an ordinary
101
+ * markdown table to walk, with every other fence still protected.
102
+ * 2. **Then, inside protection**: the consumer's `beforeLinks` pass, wikilink
103
+ * resolution, and the consumer's `afterLinks` pass. A `{@link}` tag may sit
104
+ * in prose a wikilink also touches, so the repository's own rewrites bracket
105
+ * the shared one rather than replacing it.
106
+ *
107
+ * @param {object[]} pages - Every page.
108
+ * @param {object} options - Everything the render needs.
109
+ * @returns {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}}
110
+ */
111
+ export function renderPages(pages: object[], options: object): {
112
+ written: number;
113
+ byKind: Record<string, number>;
114
+ tableErrors: object[];
115
+ wikiErrors: object[];
116
+ };
117
+ /**
118
+ * Writes the section landings a published tree needs but no note supplies.
119
+ *
120
+ * Two separate jobs, and both exist because of how Hugo decides what a section
121
+ * is:
122
+ *
123
+ * - **Declared sections** get a titled `_index.md` with their hero, so a landing
124
+ * matches the card that links to it instead of showing Hugo's auto-humanised
125
+ * directory name. The body is empty, which lets the theme list the section's
126
+ * children — or say it is empty, for a section whose content has not shipped.
127
+ * - **Every other section directly under the mount** gets a bare `_index.md`,
128
+ * or its own address publishes nothing. Hugo generates a section page
129
+ * automatically only for a *top-level* content directory; below that, a
130
+ * directory without an `_index.md` is not a section, so its URL 404s while
131
+ * its children publish normally. Mounting a tree one level down demotes every
132
+ * section it holds, and the ones with no landing of their own quietly stop
133
+ * existing while every page inside them keeps working.
134
+ *
135
+ * Scoped to one level on purpose. A directory further down was not a section
136
+ * before the move either, and giving it one here would silently re-scope the
137
+ * prev/next navigation of every page inside it.
138
+ *
139
+ * @param {string} outRoot - The mount directory.
140
+ * @param {object} options - `{ sections, landing, sectionTitle }`.
141
+ * @returns {number} How many landings were written.
142
+ */
143
+ export function writeSectionLandings(outRoot: string, { sections, landing, sectionTitle }: object): number;
144
+ /**
145
+ * A section landing's title, from its directory name — `macro` → `Macros`.
146
+ *
147
+ * Hugo derives exactly this for a section page it generates itself, but not for
148
+ * one backed by an `_index.md`: an explicit file with no `title` renders a blank
149
+ * heading. So a backfilled landing states its own, in plain English
150
+ * pluralisation rather than Hugo's inflector, which spells that section
151
+ * "Macroes".
152
+ *
153
+ * @param {string} name - The directory name.
154
+ * @returns {string} The display title.
155
+ */
156
+ export function pluralTitle(name: string): string;
157
+ /**
158
+ * Resolves `site.pass` to its bundle.
159
+ *
160
+ * @param {string|undefined} name - The configured name.
161
+ * @param {object} options - The configured options, plus `repoRoot`.
162
+ * @returns {{beforeLinks?: Function, afterLinks?: Function}} The bundle.
163
+ */
164
+ export function resolveSitePass(name: string | undefined, options: object): {
165
+ beforeLinks?: Function;
166
+ afterLinks?: Function;
167
+ };
168
+ /**
169
+ * The output root, having established that it is safe to delete.
170
+ *
171
+ * The whole tree is a build artifact and is wiped on every run, so this
172
+ * resolution is the difference between clearing a build directory and clearing
173
+ * the repository. An unset `site.out` resolves to `rootDir` itself, and the
174
+ * wipe then deletes the working tree — which is not a hypothetical: it happened
175
+ * while this module was being written, on a configuration that simply had no
176
+ * `site` section yet.
177
+ *
178
+ * So the path is refused unless it is **strictly inside** the repository root.
179
+ * Both failing shapes are ordinary rather than exotic — an absent setting, and a
180
+ * `..` that climbs out — and neither should be recoverable by being careful.
181
+ *
182
+ * @param {string} rootDir - The repository root.
183
+ * @param {string} out - The configured `site.out`.
184
+ * @returns {string} The absolute output root.
185
+ * @throws {Error} When it is unset, or is not below `rootDir`.
186
+ */
187
+ export function resolveOutputRoot(rootDir: string, out: string): string;
188
+ /**
189
+ * Builds a Hugo content tree from a content tree, and reports what it found.
190
+ *
191
+ * Returns rather than exits, in every case. A caller — the command, or a test —
192
+ * decides what a finding means; the gates below are grouped so it can report
193
+ * the first that fired and stop, which is what keeps a wall of dead links from
194
+ * burying the one manifest that caused them.
195
+ *
196
+ * @param {object} [options] - Options.
197
+ * @param {object} [options.config] - A resolved configuration; loaded when
198
+ * omitted.
199
+ * @param {string} [options.outRoot] - Override the configured output mount.
200
+ * @returns {{gates: object, stats: object|null, tableErrors: object[],
201
+ * wikiErrors: object[], manifests: object|null}}
202
+ */
203
+ export function buildSite({ config, outRoot }?: {
204
+ config?: object | undefined;
205
+ outRoot?: string | undefined;
206
+ }): {
207
+ gates: object;
208
+ stats: object | null;
209
+ tableErrors: object[];
210
+ wikiErrors: object[];
211
+ manifests: object | null;
212
+ };
213
+ export { formatUnaddressableFinding };
214
+ import { formatUnaddressableFinding } from "./foreign-manifests.mjs";
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Build the address index a site's wikilink resolver reads.
3
+ *
4
+ * @param {readonly SiteEntry[]} entries - Every page the site will publish,
5
+ * each already knowing its own `url`.
6
+ * @param {object} [options] - Cross-package inputs.
7
+ * @param {Map<string, {package: string, type?: string}>} [options.foreignIndex]
8
+ * The merged index from `loadForeignManifests`. Omit when the build publishes
9
+ * no cross-package links.
10
+ * @returns {SiteIndex} The index, and what could not be addressed unambiguously.
11
+ */
12
+ export function buildSiteIndex(entries: readonly SiteEntry[], { foreignIndex }?: {
13
+ foreignIndex?: Map<string, {
14
+ package: string;
15
+ type?: string;
16
+ }> | undefined;
17
+ }): SiteIndex;
18
+ /**
19
+ * The per-page context a wikilink resolver takes.
20
+ *
21
+ * Assembled here so a consumer spells out only what is genuinely its own — the
22
+ * source path, the citing note's type, and where errors collect — instead of
23
+ * restating the whole index every call. Both site builds wrote this object by
24
+ * hand, identically.
25
+ *
26
+ * @param {SiteIndex} built - The result of {@link buildSiteIndex}.
27
+ * @param {object} options - Per-page inputs.
28
+ * @param {string} options.src - Source path of the page being resolved, for
29
+ * diagnostics.
30
+ * @param {string|null} [options.type] - The citing note's type, which scopes a
31
+ * bare alias lookup.
32
+ * @param {object[]} options.errors - Collector the resolver appends to.
33
+ * @param {Map<string, object>} [options.foreignIndex] - The foreign index, for
34
+ * resolvers that distinguish a foreign hit from a local one.
35
+ * @param {boolean} [options.manifestsComplete] - Whether every package this
36
+ * build links into supplied a manifest. When false, a resolver may soften an
37
+ * unresolved cross-package link rather than fail.
38
+ * @returns {object} The resolver context.
39
+ */
40
+ export function wikiContext(built: SiteIndex, { src, type, errors, foreignIndex, manifestsComplete, }: {
41
+ src: string;
42
+ type?: string | null | undefined;
43
+ errors: object[];
44
+ foreignIndex?: Map<string, object> | undefined;
45
+ manifestsComplete?: boolean | undefined;
46
+ }): object;
47
+ /**
48
+ * One page the site will publish, as the index needs to see it.
49
+ */
50
+ export type SiteEntry = {
51
+ /**
52
+ * `"content"` for a note compiled from the content
53
+ * tree, anything else for a page that carries no
54
+ * `type`/`shortcode` (a developer doc, say). Only
55
+ * content entries take part in type-scoped indexing.
56
+ */
57
+ kind: string;
58
+ /**
59
+ * The note's frontmatter.
60
+ */
61
+ fm: object;
62
+ /**
63
+ * Display name.
64
+ */
65
+ name: string;
66
+ /**
67
+ * URL segment.
68
+ */
69
+ slug: string;
70
+ /**
71
+ * Section the page is filed under.
72
+ */
73
+ sec: string;
74
+ /**
75
+ * Source file's basename, e.g. `Climbing.md`.
76
+ */
77
+ base: string;
78
+ /**
79
+ * The page's published address.
80
+ */
81
+ url: string;
82
+ /**
83
+ * Whether the page is its section's landing.
84
+ */
85
+ isReadme: boolean;
86
+ };
87
+ /**
88
+ * The resolved index and everything a wikilink resolver reads beside it.
89
+ */
90
+ export type SiteIndex = {
91
+ /**
92
+ * Address → page.
93
+ */
94
+ index: Map<string, {
95
+ url: string;
96
+ name?: string;
97
+ }>;
98
+ /**
99
+ * Keys claimed by two pages, and so
100
+ * deliberately absent from `index`.
101
+ */
102
+ ambiguous: Set<string>;
103
+ /**
104
+ * `type|alias`.
105
+ */
106
+ typeAlias: Map<string, {
107
+ url: string;
108
+ name?: string;
109
+ }>;
110
+ /**
111
+ * Type-scoped aliases claimed twice.
112
+ */
113
+ typeCollide: Set<string>;
114
+ /**
115
+ * Every type the resolver should read as
116
+ * an address qualifier, local and foreign.
117
+ */
118
+ contentTypes: Set<string>;
119
+ /**
120
+ * Section names, lowercased.
121
+ */
122
+ sections: Set<string>;
123
+ /**
124
+ * `type:shortcode`
125
+ * → page, for callers resolving embedded
126
+ * references (a being's items, say).
127
+ */
128
+ refIndex: Map<string, {
129
+ name: string;
130
+ url: string;
131
+ }>;
132
+ /**
133
+ * Addresses claimed by
134
+ * more than one package. Non-empty is a
135
+ * build failure; the caller reports it.
136
+ */
137
+ conflicts: {
138
+ key: string;
139
+ package: string;
140
+ }[];
141
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Every wikilink authored inside a frontmatter value (#1428).
3
+ *
4
+ * Wikilinks are resolved in a note's **body** — by {@link resolveWebWikilinks}
5
+ * here, and by the pack compilers' `convertWikilinks` for Foundry. Frontmatter
6
+ * is not markdown and is never walked by either, so a link written in one is
7
+ * copied through verbatim and reaches the reader as literal `[[…]]` text, in
8
+ * whatever the theme renders that field as (an infobox row, a description, a
9
+ * card subtitle). Nothing downstream notices: the value is a valid string, the
10
+ * page builds, and the defect is visible only to someone who looks at it.
11
+ *
12
+ * So the form is refused rather than resolved. Resolving it would mean choosing
13
+ * an output syntax for a field whose renderer is unknown to this build — a
14
+ * markdown link is inert in a Hugo template that prints the value as text, and
15
+ * an `<a>` is unusable in one that escapes it — and would quietly bless an
16
+ * authoring habit that the pack build has no way to honour at all. Frontmatter
17
+ * carries data; a link belongs in prose.
18
+ *
19
+ * Values are read from the *parsed* frontmatter, so a `[[` inside a YAML comment
20
+ * is not a hit, and every hit can be named by the path a reader would look at.
21
+ *
22
+ * @param {unknown} fm - Parsed frontmatter, as `gray-matter` returns it.
23
+ * @returns {Array<{path: string, link: string}>} In reading order; `path` is the
24
+ * dotted key path of the offending value (`government.summary`, `aliases.1`).
25
+ */
26
+ export function frontmatterWikilinks(fm: unknown): Array<{
27
+ path: string;
28
+ link: string;
29
+ }>;
30
+ /**
31
+ * Rewrites the wikilinks in a markdown body as KB-local markdown links.
32
+ *
33
+ * A target is looked up case-insensitively: first as an alias scoped to the
34
+ * source's own **type** (`ctx.typeAlias`, keyed `type|alias`) — a note's
35
+ * directory and `category` play no part — then in the KB-wide `ctx.index` (keyed
36
+ * by the unambiguous `section/slug` and `type/shortcode`, plus name/filename/slug
37
+ * fallbacks).
38
+ *
39
+ * An unresolved target fails the build only when it is a genuine intra-KB
40
+ * problem — an ambiguous alias, or a qualified `prefix/key` whose prefix is a
41
+ * real KB section or content directory. Anything else is treated as an external
42
+ * reference — until every package's manifest is present, after which any
43
+ * `type-shortcode` address resolving nowhere fails too. Failures are collected
44
+ * in `ctx.errors`.
45
+ *
46
+ * Whether or not it fails the build, a target that resolves nowhere renders
47
+ * through {@link unresolvedLink} rather than as bare prose (#1665): the author's
48
+ * text is kept, marked so a reader can see a link was intended. Not failing the
49
+ * build is a statement that the link *may* be legitimate prose — it was never a
50
+ * reason to make a dead link indistinguishable from the sentence around it.
51
+ *
52
+ * A target that **resolved** to an entry with no page is not this case and is
53
+ * not marked: a pack-only package (#1516) publishes Foundry addresses and no
54
+ * web pages, so the author wrote a real address and there is simply nothing to
55
+ * link to.
56
+ *
57
+ * @param {string} body - The markdown body.
58
+ * @param {object} ctx - `{ index, typeAlias, collide, typeCollide, sections,
59
+ * contentTypes, foreign, manifestsComplete, type, errors, src }`. `foreign`
60
+ * is the cross-package manifest index (#1446); `manifestsComplete` says
61
+ * whether every linkable package is accounted for. Together they decide
62
+ * whether an unresolved address is a typo or a package merely absent.
63
+ * @returns {string} The body with wikilinks rewritten.
64
+ */
65
+ export function resolveWebWikilinks(body: string, ctx: object): string;
66
+ export { slugify };
67
+ import { slugify } from "./content-slug.mjs";
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The parts of a wikilink's interior.
3
+ *
4
+ * @typedef {object} ParsedWikilink
5
+ * @property {string} inner - The whole interior, unescaped and trimmed. What an
6
+ * unlabelled link displays, anchor and all.
7
+ * @property {string} target - What is linked to: an address, an alias, or `""`
8
+ * for a link to a section of the same page.
9
+ * @property {string} anchor - The `#section` slug, `""` when there is none.
10
+ * @property {string|null} display - The text after `|`, or `null` when the link
11
+ * is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
12
+ * @property {boolean} labelled - Whether a `|` was present at all. What an
13
+ * unlabelled link shows depends on whether its target read as an address
14
+ * (SoHL#1409), so the caller needs to know.
15
+ */
16
+ /**
17
+ * Split a wikilink's interior into its parts.
18
+ *
19
+ * Takes the **inside** of the brackets — the capture group of {@link WIKILINK} —
20
+ * not the whole link, so a caller that has already matched does not re-match.
21
+ *
22
+ * @param {string} rawInner - The text between `[[` and `]]`.
23
+ * @returns {ParsedWikilink} The parts, each trimmed.
24
+ */
25
+ export function parseWikilink(rawInner: string): ParsedWikilink;
26
+ /**
27
+ * Whether a parsed link addresses a section of the page it is written on.
28
+ *
29
+ * `[[#some-heading]]` — no target, only an anchor. Both resolvers special-case
30
+ * it before consulting any index, because there is nothing to look up.
31
+ *
32
+ * @param {ParsedWikilink} parsed - A parsed wikilink.
33
+ * @returns {boolean} True when the link is same-page.
34
+ */
35
+ export function isSamePage({ target, anchor }: ParsedWikilink): boolean;
36
+ /**
37
+ * What a `[[…]]` **is**, before anything decides where it points.
38
+ *
39
+ * One authored link compiles to two different addresses — a Foundry `@UUID`
40
+ * enricher for the packs, a URL for the web — and those two destinations are the
41
+ * *only* thing that legitimately differs. The syntax is the author's, and it is
42
+ * the same syntax whichever build is reading it.
43
+ *
44
+ * It was written twice, and the two copies had already drifted:
45
+ *
46
+ * | Body | `foundry` side | `web` side |
47
+ * | --- | --- | --- |
48
+ * | `[[weapongear-bsw` ⏎ `]]` in a table cell | not a link | a link whose target ends in a newline |
49
+ * | a stray `[[`, a real link two paragraphs later | finds the real link | swallows both paragraphs as one target |
50
+ *
51
+ * The web side's pattern omitted `\n` from the excluded set, so an unclosed
52
+ * bracket consumed everything up to the next `]]` anywhere in the document —
53
+ * the same shape of corruption a hand-rolled code-fence regex caused on the one
54
+ * page whose subject is link syntax (SoHL#1665). It was also internally
55
+ * inconsistent: its *frontmatter* scan excluded newlines while its body scan did
56
+ * not.
57
+ *
58
+ * So the parse lives here, once. A resolver receives the parts and decides only
59
+ * what it is actually for: which address space the target belongs to.
60
+ *
61
+ * @module
62
+ */
63
+ /**
64
+ * A wikilink, as authored.
65
+ *
66
+ * **Newlines are excluded deliberately.** A link is written on one line; an
67
+ * unclosed `[[` is a typo, and the alternative is letting it swallow arbitrary
68
+ * prose in search of a closer. Erring towards "not a link" leaves the author's
69
+ * text as written, which is the safe direction for a rewriter.
70
+ */
71
+ export const WIKILINK: RegExp;
72
+ /**
73
+ * The parts of a wikilink's interior.
74
+ */
75
+ export type ParsedWikilink = {
76
+ /**
77
+ * - The whole interior, unescaped and trimmed. What an
78
+ * unlabelled link displays, anchor and all.
79
+ */
80
+ inner: string;
81
+ /**
82
+ * - What is linked to: an address, an alias, or `""`
83
+ * for a link to a section of the same page.
84
+ */
85
+ target: string;
86
+ /**
87
+ * - The `#section` slug, `""` when there is none.
88
+ */
89
+ anchor: string;
90
+ /**
91
+ * - The text after `|`, or `null` when the link
92
+ * is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
93
+ */
94
+ display: string | null;
95
+ /**
96
+ * - Whether a `|` was present at all. What an
97
+ * unlabelled link shows depends on whether its target read as an address
98
+ * (SoHL#1409), so the caller needs to know.
99
+ */
100
+ labelled: boolean;
101
+ };