@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,571 @@
1
+ /**
2
+ * Validate and normalize a content configuration.
3
+ *
4
+ * Every configuration reaches this function — a YAML one through the loader in
5
+ * `engine/pack-config.mjs`, an `.mjs` one by calling it itself — so that a
6
+ * malformed configuration fails at load with a message naming the offending
7
+ * field, rather than surfacing much later as an empty pack or a missing asset.
8
+ * The returned object is a deeply frozen **copy**: mutating the input
9
+ * afterwards cannot reach the configuration the build reads.
10
+ *
11
+ * @param {ContentBuildConfigInput} config The configuration to validate.
12
+ * @returns {ContentBuildConfig} The frozen, defaulted configuration.
13
+ * @throws {TypeError} If any field is missing, mistyped, or unrecognized.
14
+ */
15
+ export function defineConfig(config: ContentBuildConfigInput): ContentBuildConfig;
16
+ /**
17
+ * The two kinds of Foundry package a content module can be built into. The
18
+ * value is also the directory Foundry installs the package under, which is why
19
+ * it is plural.
20
+ *
21
+ * @satisfies {readonly PackageKind[]}
22
+ */
23
+ export const PACKAGE_KINDS: readonly ["systems", "modules"];
24
+ export namespace DEFAULT_PATHS {
25
+ let content: "assets/content";
26
+ let manifests: "assets/manifests";
27
+ let manifestOut: "build/manifests";
28
+ let packJson: "build/packs-json";
29
+ let stage: "build/stage/packs";
30
+ let unpack: "build/tmp/packs";
31
+ let foreignCache: "build/cache/foreign";
32
+ }
33
+ /**
34
+ * The Foundry document types a compendium pack may hold. This is the set the
35
+ * toolchain is able to compile a pack of; a document type Foundry supports but
36
+ * this toolchain does not compile is deliberately absent (see #1501 — playlists
37
+ * and roll tables are out of scope).
38
+ *
39
+ * @satisfies {readonly PackDocumentType[]}
40
+ */
41
+ export const PACK_DOCUMENT_TYPES: readonly ["Actor", "Adventure", "Item", "JournalEntry", "Macro", "Scene"];
42
+ /**
43
+ * The landing-page rules a repository may route by.
44
+ *
45
+ * A *landing page* is a note that addresses a whole section rather than a page
46
+ * within one, so it has no slug of its own. Which note that is differs between
47
+ * repositories, and both live rules are represented here because switching
48
+ * either one on for the other repository moves addresses that are already
49
+ * published:
50
+ *
51
+ * - `readme` — a `README.md` **is** its section's landing page. This is `sohl`'s
52
+ * rule, and a `doc` note routes by its `category` like any other, so `sohl`'s
53
+ * eleven `category: collection` notes publish under a literal `collection/`
54
+ * section (`kb/collection/skills/`).
55
+ * - `collection` — a `doc` note whose `category` is `collection` addresses the
56
+ * section it introduces, named by its authored `section`. This is `thalorna`'s
57
+ * rule, under which the same note publishes at `creature/`.
58
+ *
59
+ * The two are not disjoint and cannot simply both apply: each tree holds notes
60
+ * the other rule would move.
61
+ *
62
+ * @type {readonly string[]}
63
+ */
64
+ export const LANDING_RULES: readonly string[];
65
+ /**
66
+ * A repository's address scheme, with the defaults an unconfigured one gets.
67
+ *
68
+ * `prefix` is where the content tree mounts *inside the package* — `"kb/"` for
69
+ * `sohl`, whose knowledgebase is one surface among several, and empty for
70
+ * `thalorna`, whose site is nothing but its content. It is not the package's
71
+ * own mount point: where the package itself is served is the consuming build's
72
+ * knowledge, held in `PACKAGE_BASE` (`engine/kb-manifest.mjs`) and prefixed at
73
+ * resolve time, so it is never recorded here (#1465).
74
+ */
75
+ export const DEFAULT_ADDRESS_SCHEME: Readonly<{
76
+ prefix: "";
77
+ landing: "readme";
78
+ }>;
79
+ export type PackageKind = "systems" | "modules";
80
+ export type PackDocumentType = "Actor" | "Adventure" | "Item" | "JournalEntry" | "Macro" | "Scene";
81
+ /**
82
+ * One compendium pack the build compiles, named exactly as it is declared in
83
+ * the package manifest's `packs` array.
84
+ *
85
+ * Several packs may share a `type`. The `type` selects the **compiler** that
86
+ * fills the pack; a note's `pack:` frontmatter selects **which pack of that
87
+ * type** receives its document. The two are orthogonal, and both are needed
88
+ * once a repository groups same-type documents editorially — which it may have
89
+ * to, since a compendium UUID carries its pack name and collapsing such a
90
+ * layout breaks every stored reference (#1566).
91
+ */
92
+ export type PackSpec = {
93
+ /**
94
+ * Pack name — the manifest `name`, and the
95
+ * directory under `packs/`.
96
+ */
97
+ name: string;
98
+ /**
99
+ * Foundry document type the pack holds.
100
+ */
101
+ type: PackDocumentType;
102
+ /**
103
+ * Human-readable label. Defaults to `name`.
104
+ */
105
+ label?: string | undefined;
106
+ /**
107
+ * Whether the pack is GM-only. Default `false`.
108
+ */
109
+ private?: boolean | undefined;
110
+ /**
111
+ * The pack's folder-hierarchy file, relative
112
+ * to `paths.content`. Default `null` — no
113
+ * folder documents are emitted.
114
+ */
115
+ folders?: string | null | undefined;
116
+ /**
117
+ * Packs written by this pack's own compiler
118
+ * pass rather than a pass of their own (the
119
+ * scenes pass also emits the adventures
120
+ * bundling them). Default `[]`.
121
+ */
122
+ companions?: PackSpec[] | undefined;
123
+ /**
124
+ * Whether a pass compiling zero entries is
125
+ * legitimate rather than a build failure.
126
+ * Default `false`.
127
+ */
128
+ mayBeEmpty?: boolean | undefined;
129
+ /**
130
+ * Whether this is the pack of its `type`
131
+ * that receives notes declaring no `pack:`
132
+ * of their own. Default `false`. A type
133
+ * with exactly one pack is its default
134
+ * implicitly; a type with several and no
135
+ * `default: true` requires every note of
136
+ * that type to declare one. Not permitted
137
+ * on a companion — no note is routed into
138
+ * one. See `engine/pack-router.mjs`.
139
+ */
140
+ default?: boolean | undefined;
141
+ };
142
+ /**
143
+ * The normalized form of a {@link PackSpec}: every optional half filled in.
144
+ */
145
+ export type ResolvedPackSpec = {
146
+ name: string;
147
+ type: PackDocumentType;
148
+ label: string;
149
+ private: boolean;
150
+ folders: string | null;
151
+ companions: readonly Readonly<ResolvedPackSpec>[];
152
+ mayBeEmpty: boolean;
153
+ default: boolean;
154
+ };
155
+ /**
156
+ * The directories a consumer may relocate, each relative to `rootDir`.
157
+ */
158
+ export type PathsInput = {
159
+ /**
160
+ * Content tree root.
161
+ */
162
+ content?: string | undefined;
163
+ /**
164
+ * Vendored cross-package link manifests,
165
+ * read by `links`. Inbound.
166
+ */
167
+ manifests?: string | undefined;
168
+ /**
169
+ * Where `manifest` writes this package's
170
+ * own link manifest. Outbound, and a
171
+ * build artifact — the published copy is
172
+ * the one a consumer vendors into its
173
+ * `manifests` directory.
174
+ */
175
+ manifestOut?: string | undefined;
176
+ /**
177
+ * Build-only per-entry JSON intermediate.
178
+ */
179
+ packJson?: string | undefined;
180
+ /**
181
+ * Compiled LevelDB packs.
182
+ */
183
+ stage?: string | undefined;
184
+ /**
185
+ * Where `unpack` extracts JSON back to.
186
+ */
187
+ unpack?: string | undefined;
188
+ };
189
+ /**
190
+ * {@link PathsInput}, resolved to absolute paths against `rootDir`.
191
+ */
192
+ export type ResolvedPaths = {
193
+ content: string;
194
+ manifests: string;
195
+ manifestOut: string;
196
+ packJson: string;
197
+ stage: string;
198
+ unpack: string;
199
+ };
200
+ /**
201
+ * The identity every compiled document's `_stats` block carries.
202
+ *
203
+ * `coreVersion` is **not** here: it is the top-level `compatibility.minimum`,
204
+ * so the floor is declared in one place and stamped from it.
205
+ */
206
+ export type StatsSpec = {
207
+ /**
208
+ * The game system the documents are for —
209
+ * `"sohl"` even for a module, which ships
210
+ * content *for* the system rather than being it.
211
+ */
212
+ systemId: string;
213
+ /**
214
+ * The system version the packs were built against.
215
+ */
216
+ systemVersion: string;
217
+ /**
218
+ * The 16-character id stamped as the author.
219
+ */
220
+ lastModifiedBy: string;
221
+ };
222
+ /**
223
+ * The section of the configuration belonging to `@heroiclands/package-build`.
224
+ *
225
+ * **Opaque here, on purpose.** One repository describes itself in one file, so
226
+ * the two shared build packages share it — but they split by *input*, and
227
+ * neither should learn the other's schema. This validator checks only that the
228
+ * section is a mapping and hands it back frozen; package-build validates what
229
+ * is inside it, exactly as this module validates the keys around it.
230
+ *
231
+ * That is also why it is a section rather than a scatter of top-level keys: one
232
+ * reserved name keeps {@link ContentBuildConfig}'s unknown-key guard intact for
233
+ * everything else, which is the guard that catches a typo'd `packs` before it
234
+ * becomes an empty compendium.
235
+ *
236
+ * The values package-build needs that are *not* in here — `packageKind`,
237
+ * `foundryPackage` — it reads from the top level, where they already are. They
238
+ * were duplicated in each consumer's deploy script until this existed, which is
239
+ * two places for one fact.
240
+ */
241
+ export type PackageBuildSection = Record<string, unknown>;
242
+ /**
243
+ * The two manifest switches. A package may publish a link manifest, consume
244
+ * other packages' manifests, both, or neither — the four combinations are all
245
+ * real (see #1385/#1446: `kethira` consumes but never publishes).
246
+ */
247
+ export type ManifestSwitches = {
248
+ /**
249
+ * Emit this package's link manifest.
250
+ */
251
+ publish: boolean;
252
+ /**
253
+ * Resolve cross-package links through vendored manifests.
254
+ */
255
+ consume: boolean;
256
+ };
257
+ export type PublishSwitches = {
258
+ /**
259
+ * Render this package's knowledgebase/site pages.
260
+ */
261
+ site: boolean;
262
+ manifests: ManifestSwitches;
263
+ };
264
+ export type ManifestSwitchesInput = {
265
+ publish?: boolean | undefined;
266
+ consume?: boolean | undefined;
267
+ };
268
+ /**
269
+ * The **Foundry core** version range this package supports.
270
+ *
271
+ * `minimum` is stamped into every compiled document as `_stats.coreVersion`, so
272
+ * a document never claims to predate the migrations that would rewrite it.
273
+ *
274
+ * `verified` names the newest build the full suite has **actually passed** —
275
+ * never an aspiration. Moving this out of the hand-authored manifest and into a
276
+ * configuration file does not soften that; if anything it makes the claim
277
+ * easier to edit casually, so it is written down here beside the key rather
278
+ * than left behind in the template.
279
+ *
280
+ * Not to be confused with `relationships.systems[].compatibility`, which is the
281
+ * **game system's** version range. Same key, different subject.
282
+ */
283
+ export type CompatibilitySpec = {
284
+ /**
285
+ * Oldest Foundry core this package supports.
286
+ */
287
+ minimum: string;
288
+ /**
289
+ * Newest Foundry core the suite has passed on.
290
+ */
291
+ verified?: string | undefined;
292
+ };
293
+ /**
294
+ * What this package declares about other packages, in Foundry's own shape.
295
+ *
296
+ * Passed through to the shipped manifest, and read here for one derivation: a
297
+ * module's `_stats.systemVersion` comes from the `verified` field of the system
298
+ * it declares a relationship with, because a module's own `package.json`
299
+ * version is the *module's* and stamping it would claim a system version that
300
+ * never existed.
301
+ */
302
+ export type Relationships = {
303
+ /**
304
+ * Game systems this package targets.
305
+ */
306
+ systems?: RelationshipSpec[] | undefined;
307
+ /**
308
+ * Packages this one needs.
309
+ */
310
+ requires?: RelationshipSpec[] | undefined;
311
+ /**
312
+ * Packages it works well with.
313
+ */
314
+ recommends?: RelationshipSpec[] | undefined;
315
+ /**
316
+ * Packages it cannot run beside.
317
+ */
318
+ conflicts?: RelationshipSpec[] | undefined;
319
+ };
320
+ /**
321
+ * One declared relationship.
322
+ */
323
+ export type RelationshipSpec = {
324
+ /**
325
+ * The other package's id.
326
+ */
327
+ id: string;
328
+ /**
329
+ * `system`, `module`, or `world`.
330
+ */
331
+ type?: string | undefined;
332
+ /**
333
+ * Where its manifest is published.
334
+ */
335
+ manifest?: string | undefined;
336
+ /**
337
+ * The version range of *that*
338
+ * package this one targets — for a system
339
+ * relationship, `verified` is what
340
+ * `_stats.systemVersion` is stamped from.
341
+ */
342
+ compatibility?: CompatibilitySpec | undefined;
343
+ };
344
+ /**
345
+ * How a generated documentation page is framed in the repository publishing it.
346
+ *
347
+ * The tables come from the `itemBuilders` registry and are the same wherever
348
+ * they are rendered. Everything around them is the consumer's: the heading, the
349
+ * "See also" line its section's pages carry, the orientation a reader needs
350
+ * before the tables start, and where the page is filed. Those were the reason
351
+ * every consumer wrapped the renderer in a script of its own.
352
+ */
353
+ export type DocPageSpec = {
354
+ /**
355
+ * The page's H1.
356
+ */
357
+ title?: string | undefined;
358
+ /**
359
+ * Where to write it, relative to `rootDir`.
360
+ * Without it the page goes to stdout.
361
+ */
362
+ out?: string | undefined;
363
+ /**
364
+ * Lines between the generated banner and the
365
+ * first table. Markdown, emitted verbatim.
366
+ */
367
+ preamble?: string[] | undefined;
368
+ };
369
+ /**
370
+ * The documentation pages this repository generates.
371
+ */
372
+ export type DocsSpec = {
373
+ /**
374
+ * The item-frontmatter reference,
375
+ * rendered by `content-build docs
376
+ * item-fields`.
377
+ */
378
+ itemFields?: DocPageSpec | undefined;
379
+ };
380
+ export type PublishSwitchesInput = {
381
+ site?: boolean | undefined;
382
+ manifests?: ManifestSwitchesInput | undefined;
383
+ };
384
+ /**
385
+ * One entry of a consumer's `itemBuilders` registry.
386
+ *
387
+ * Either a bare builder function, or that builder paired with the type's
388
+ * default art and the frontmatter fields it declares. See
389
+ * {@link normalizeItemBuilders} for why the paired form exists.
390
+ *
391
+ * `fields` is what makes the type documentable: a builder function says
392
+ * nothing about the vocabulary it consumes, so a consumer that declares its
393
+ * fields can generate its own authoring reference and check its own notes,
394
+ * while one that does not is simply undocumented rather than broken (#22).
395
+ */
396
+ export type ItemBuilderEntry = ((fm: object) => object) | {
397
+ system: (fm: object) => object;
398
+ img?: string;
399
+ fields?: readonly object[];
400
+ };
401
+ /**
402
+ * The configuration a consumer writes.
403
+ */
404
+ export type ContentBuildConfigInput = {
405
+ /**
406
+ * Absolute path of the consuming
407
+ * repository — every configured path is
408
+ * resolved against it, so the build never
409
+ * depends on the working directory.
410
+ */
411
+ rootDir: string;
412
+ /**
413
+ * Content package name — the value each
414
+ * content note carries in its `package:`
415
+ * frontmatter.
416
+ */
417
+ contentPackage: string;
418
+ /**
419
+ * Foundry package id, as it appears in
420
+ * `system.json` / `module.json`.
421
+ */
422
+ foundryPackage: string;
423
+ /**
424
+ * Whether the package is a system or a module.
425
+ */
426
+ packageKind: PackageKind;
427
+ /**
428
+ * Identity stamped into every document's `_stats`.
429
+ */
430
+ stats: StatsSpec;
431
+ /**
432
+ * The consumer's
433
+ * item-type registry: each content `type`
434
+ * that compiles into an Item, paired with
435
+ * the builder producing its `system` block
436
+ * — and, optionally, the default art a
437
+ * note of that type gets when it sets no
438
+ * `img:` of its own. Default `{}` — a
439
+ * content module that ships no items
440
+ * declares none.
441
+ */
442
+ itemBuilders?: Record<string, ItemBuilderEntry> | undefined;
443
+ /**
444
+ * Packs to compile. More than one entry
445
+ * may share a `type`: a note then names
446
+ * the pack it belongs in with its
447
+ * `pack:` frontmatter, and one pack of
448
+ * the type is marked `default: true` to
449
+ * receive the notes that name none
450
+ * (#1566).
451
+ */
452
+ packs: PackSpec[];
453
+ /**
454
+ * Layout overrides. See {@link DEFAULT_PATHS}.
455
+ */
456
+ paths?: PathsInput | undefined;
457
+ /**
458
+ * Directory names the content walk ignores
459
+ * wherever they appear (e.g. Obsidian's
460
+ * `Templates`). Default `[]`.
461
+ */
462
+ skipDirectories?: string[] | undefined;
463
+ /**
464
+ * Reserved for
465
+ * `@heroiclands/package-build`, which
466
+ * validates it. Not read here.
467
+ */
468
+ packageBuild?: PackageBuildSection | undefined;
469
+ /**
470
+ * How this repository frames the
471
+ * documentation pages it generates.
472
+ */
473
+ docs?: DocsSpec | undefined;
474
+ /**
475
+ * The Foundry core range this
476
+ * package supports. Required for any
477
+ * repository that ships one — reading
478
+ * the floor throws without it — and
479
+ * absent for a content-only consumer,
480
+ * which has none to invent.
481
+ */
482
+ compatibility?: CompatibilitySpec | undefined;
483
+ /**
484
+ * What this package declares about
485
+ * others, in Foundry's own shape.
486
+ */
487
+ relationships?: Relationships | undefined;
488
+ /**
489
+ * Publishing switches. Each defaults to off.
490
+ */
491
+ publish?: PublishSwitchesInput | undefined;
492
+ };
493
+ /**
494
+ * The normalized, frozen configuration the toolchain reads.
495
+ */
496
+ export type ContentBuildConfig = {
497
+ rootDir: string;
498
+ contentPackage: string;
499
+ foundryPackage: string;
500
+ packageKind: PackageKind;
501
+ /**
502
+ * Derived: the served Foundry asset root,
503
+ * `<packageKind>/<foundryPackage>/assets`.
504
+ */
505
+ assetRoot: string;
506
+ paths: Readonly<ResolvedPaths>;
507
+ stats: Readonly<StatsSpec>;
508
+ /**
509
+ * Derived: the
510
+ * `system` builder of each entry, whichever
511
+ * of the two spellings declared it.
512
+ */
513
+ itemBuilders: Readonly<Record<string, Function>>;
514
+ /**
515
+ * Derived: the default art
516
+ * of each entry that paired one. Sparse — a
517
+ * type absent here has no default, and a note
518
+ * of it must carry `img:` (#7).
519
+ */
520
+ itemArt: Readonly<Record<string, string>>;
521
+ /**
522
+ * Derived:
523
+ * the frontmatter fields each entry
524
+ * declared. Sparse, like `itemArt` — a type
525
+ * absent here compiles normally and is
526
+ * simply undocumented (#22).
527
+ */
528
+ itemFields: Readonly<Record<string, readonly object[]>>;
529
+ /**
530
+ * Derived: the keys of
531
+ * {@link ContentBuildConfigInput.itemBuilders},
532
+ * so the accepted item types and the builder
533
+ * table are one list (#1504).
534
+ */
535
+ itemTypes: ReadonlySet<string>;
536
+ /**
537
+ * Derived: every type whose prose
538
+ * compiles into a JournalEntry of its own —
539
+ * the item types, plus `macro`, plus the map
540
+ * types. The one set the compilers and the
541
+ * link-manifest emitter both read.
542
+ */
543
+ docEntryTypes: ReadonlySet<string>;
544
+ skipDirectories: readonly string[];
545
+ packs: readonly Readonly<ResolvedPackSpec>[];
546
+ /**
547
+ * Derived: every pack directory
548
+ * the build produces, in compile order —
549
+ * each pack followed by its companions.
550
+ */
551
+ packDirectories: readonly string[];
552
+ /**
553
+ * Passed through
554
+ * frozen, uninterpreted. `{}` when absent.
555
+ */
556
+ packageBuild: Readonly<PackageBuildSection>;
557
+ /**
558
+ * Frozen; `{}` when absent.
559
+ */
560
+ docs: Readonly<DocsSpec>;
561
+ /**
562
+ * The Foundry core
563
+ * range, or `null` when none is declared.
564
+ */
565
+ compatibility: Readonly<CompatibilitySpec> | null;
566
+ /**
567
+ * Frozen; `{}` when absent.
568
+ */
569
+ relationships: Readonly<Relationships>;
570
+ publish: Readonly<PublishSwitches>;
571
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Replace whole words with their abbreviations.
3
+ *
4
+ * Greedy and longest-first: at each position the longest run of tokens that
5
+ * names an entry wins, so `tribunus militum` never abbreviates as
6
+ * `trib militum`, and `countess` is never reached by `count`'s rule.
7
+ *
8
+ * @param {readonly string[]} tokens - Lowercased, alphanumeric-only tokens.
9
+ * @returns {string[]} The tokens, with each matched run replaced by one token.
10
+ */
11
+ export function abbreviateTokens(tokens: readonly string[]): string[];
12
+ /**
13
+ * The abbreviations a slug may use to stay short.
14
+ *
15
+ * Names in this setting are long and formulaic — a rank, an office, a material,
16
+ * a unit — so a slug built from one runs to a mouthful that says little more
17
+ * than a short one would. These are the conventional shortenings for that
18
+ * vocabulary.
19
+ *
20
+ * **Whole words only.** `count` abbreviates, `countess` does not become
21
+ * `ctess`: it has its own entry. Matching is greedy and longest-first, so a
22
+ * multi-word phrase wins over its own first word — `tribunus militum` is
23
+ * `tribmil`, a bare `tribunus` is `trib` — and a longer word wins over a
24
+ * shorter one that prefixes it.
25
+ *
26
+ * **Abbreviations are not unique, and that is the caller's problem.** Several
27
+ * words share one: `abbess` and `abbot` are both `abb`, `monk` and `brother`
28
+ * both `br`, `emperor` and `empress` both `emp`. Two names that differ only in
29
+ * such a word therefore slug alike, which for a URL is a collision — the
30
+ * build's existing slug-collision guard is what catches it, and the fix is a
31
+ * more specific name.
32
+ *
33
+ * @module
34
+ */
35
+ /**
36
+ * Word (or phrase) → abbreviation.
37
+ *
38
+ * Keys are lowercase and space-separated as they read; a hyphenated key like
39
+ * `shire-reeve` is stored with its hyphen because that is how the word is
40
+ * written, and the matcher tokenises the same way it tokenises a name.
41
+ *
42
+ * @type {Readonly<Record<string, string>>}
43
+ */
44
+ export const ABBREVIATIONS: Readonly<Record<string, string>>;