@heroiclands/package-build 17.1.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +1773 -0
  2. package/CONTENT.md +288 -72
  3. package/README.md +1 -1
  4. package/bin/content-build.mjs +218 -137
  5. package/bin/package-build.mjs +43 -0
  6. package/content-config.mjs +62 -67
  7. package/docs/content-format.md +768 -58
  8. package/engine/actor-compiler.mjs +586 -0
  9. package/engine/address-charset.mjs +11 -8
  10. package/engine/address-diff.mjs +266 -22
  11. package/engine/anchored-sections.mjs +83 -0
  12. package/engine/anchors.mjs +83 -0
  13. package/engine/base-compiler.mjs +148 -18
  14. package/engine/bundle-notes.mjs +276 -0
  15. package/engine/bundles.mjs +307 -0
  16. package/engine/code-fences.mjs +103 -0
  17. package/engine/compile-corpus.mjs +89 -0
  18. package/engine/content-address.mjs +267 -5
  19. package/engine/content-format-check.mjs +54 -11
  20. package/engine/content-format.mjs +37 -5
  21. package/engine/content-index.mjs +394 -111
  22. package/engine/content-links.mjs +185 -77
  23. package/engine/content-lint.mjs +232 -14
  24. package/engine/content-package.mjs +2 -1
  25. package/engine/content-tables.mjs +99 -3
  26. package/engine/document-subtypes.mjs +49 -6
  27. package/engine/field-reference.mjs +1 -1
  28. package/engine/field-spec.mjs +56 -6
  29. package/engine/folder-notes.mjs +470 -0
  30. package/engine/foreign-catalog.mjs +195 -4
  31. package/engine/{manifest-emit.mjs → foundry-entries.mjs} +78 -81
  32. package/engine/frontmatter-lint.mjs +245 -19
  33. package/engine/frontmatter.mjs +88 -8
  34. package/engine/generate.mjs +304 -13
  35. package/engine/helpers.mjs +293 -64
  36. package/engine/ids.mjs +137 -2
  37. package/engine/index-records.mjs +126 -0
  38. package/engine/index.mjs +23 -5
  39. package/engine/item-compiler.mjs +349 -0
  40. package/engine/item-docs.mjs +7 -1
  41. package/engine/item-registry.mjs +6 -0
  42. package/engine/journals.mjs +93 -27
  43. package/engine/macros.mjs +4 -2
  44. package/engine/metadata-index.mjs +495 -0
  45. package/engine/note-claims.mjs +146 -25
  46. package/engine/note-ids.mjs +112 -0
  47. package/engine/note-renames.mjs +134 -0
  48. package/engine/note-schemas.mjs +25 -0
  49. package/engine/note-vocabulary.mjs +110 -27
  50. package/engine/pack-config.mjs +4 -0
  51. package/engine/pack-router.mjs +23 -0
  52. package/engine/retired-fields.mjs +137 -3
  53. package/engine/scenes.mjs +66 -34
  54. package/engine/schema-check.mjs +58 -17
  55. package/engine/site-build.mjs +71 -12
  56. package/engine/site-index.mjs +20 -7
  57. package/engine/sql-tables.mjs +485 -0
  58. package/engine/subtype-registry.mjs +102 -0
  59. package/engine/system-block.mjs +96 -12
  60. package/engine/systems.mjs +201 -0
  61. package/engine/web-wikilinks.mjs +50 -20
  62. package/engine/wikilink-syntax.mjs +17 -8
  63. package/engine/wikilinks.mjs +124 -51
  64. package/engine/yaml-lint.mjs +282 -0
  65. package/hm3/actors.mjs +352 -0
  66. package/hm3/default-item-art.mjs +75 -0
  67. package/hm3/document-subtypes.mjs +134 -0
  68. package/hm3/index.mjs +56 -0
  69. package/hm3/item-builders.mjs +84 -0
  70. package/hm3/item-fields.mjs +180 -0
  71. package/hm3/items.mjs +96 -0
  72. package/hm3/template-priority.mjs +85 -0
  73. package/labels.mjs +191 -0
  74. package/manifest.mjs +43 -2
  75. package/package.json +19 -4
  76. package/release.mjs +62 -7
  77. package/sohl/actors.mjs +33 -487
  78. package/sohl/being-info.mjs +16 -7
  79. package/sohl/default-item-art.mjs +14 -3
  80. package/sohl/document-subtypes.mjs +16 -10
  81. package/sohl/item-builders.mjs +14 -5
  82. package/sohl/item-fields.mjs +68 -7
  83. package/sohl/items.mjs +44 -258
  84. package/sohl/note-schemas.mjs +2 -2
  85. package/types/content-config.d.mts +21 -42
  86. package/types/engine/actor-compiler.d.mts +204 -0
  87. package/types/engine/address-charset.d.mts +11 -8
  88. package/types/engine/address-diff.d.mts +53 -5
  89. package/types/engine/anchored-sections.d.mts +21 -0
  90. package/types/engine/anchors.d.mts +20 -0
  91. package/types/engine/base-compiler.d.mts +17 -17
  92. package/types/engine/bundle-notes.d.mts +173 -0
  93. package/types/engine/bundles.d.mts +60 -0
  94. package/types/engine/code-fences.d.mts +43 -0
  95. package/types/engine/compile-corpus.d.mts +32 -0
  96. package/types/engine/content-address.d.mts +205 -5
  97. package/types/engine/content-format-check.d.mts +6 -2
  98. package/types/engine/content-format.d.mts +57 -1
  99. package/types/engine/content-index.d.mts +64 -80
  100. package/types/engine/content-links.d.mts +52 -7
  101. package/types/engine/content-lint.d.mts +10 -1
  102. package/types/engine/content-package.d.mts +2 -1
  103. package/types/engine/content-tables.d.mts +21 -39
  104. package/types/engine/document-subtypes.d.mts +37 -3
  105. package/types/engine/field-spec.d.mts +76 -5
  106. package/types/engine/folder-notes.d.mts +159 -0
  107. package/types/engine/foreign-catalog.d.mts +53 -0
  108. package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +16 -38
  109. package/types/engine/frontmatter-lint.d.mts +10 -2
  110. package/types/engine/frontmatter.d.mts +64 -0
  111. package/types/engine/generate.d.mts +38 -0
  112. package/types/engine/helpers.d.mts +94 -30
  113. package/types/engine/ids.d.mts +96 -0
  114. package/types/engine/index-records.d.mts +68 -0
  115. package/types/engine/index.d.mts +9 -3
  116. package/types/engine/item-compiler.d.mts +131 -0
  117. package/types/engine/journals.d.mts +47 -9
  118. package/types/engine/metadata-index.d.mts +226 -0
  119. package/types/engine/note-claims.d.mts +57 -11
  120. package/types/engine/note-ids.d.mts +38 -0
  121. package/types/engine/note-renames.d.mts +102 -0
  122. package/types/engine/note-vocabulary.d.mts +44 -8
  123. package/types/engine/retired-fields.d.mts +75 -0
  124. package/types/engine/scenes.d.mts +3 -2
  125. package/types/engine/schema-check.d.mts +25 -4
  126. package/types/engine/site-build.d.mts +4 -4
  127. package/types/engine/site-index.d.mts +1 -1
  128. package/types/engine/sql-tables.d.mts +185 -0
  129. package/types/engine/subtype-registry.d.mts +49 -0
  130. package/types/engine/system-block.d.mts +40 -1
  131. package/types/engine/systems.d.mts +106 -0
  132. package/types/engine/web-wikilinks.d.mts +4 -2
  133. package/types/engine/wikilink-syntax.d.mts +10 -3
  134. package/types/engine/wikilinks.d.mts +41 -13
  135. package/types/engine/yaml-lint.d.mts +107 -0
  136. package/types/hm3/actors.d.mts +48 -0
  137. package/types/hm3/default-item-art.d.mts +42 -0
  138. package/types/hm3/document-subtypes.d.mts +24 -0
  139. package/types/hm3/index.d.mts +7 -0
  140. package/types/hm3/item-builders.d.mts +11 -0
  141. package/types/hm3/item-fields.d.mts +12 -0
  142. package/types/hm3/items.d.mts +23 -0
  143. package/types/hm3/template-priority.d.mts +21 -0
  144. package/types/manifest.d.mts +22 -1
  145. package/types/release.d.mts +9 -4
  146. package/types/sohl/actors.d.mts +7 -74
  147. package/types/sohl/being-info.d.mts +8 -27
  148. package/types/sohl/default-item-art.d.mts +5 -3
  149. package/types/sohl/items.d.mts +17 -32
  150. package/engine/foreign-manifests.mjs +0 -126
  151. package/engine/kb-manifest.mjs +0 -490
  152. package/types/engine/foreign-manifests.d.mts +0 -43
  153. package/types/engine/kb-manifest.d.mts +0 -241
@@ -0,0 +1,159 @@
1
+ /**
2
+ * An authored `parent` with any wikilink brackets and label stripped.
3
+ *
4
+ * The specification types `parent` as a `WikiLink`, and a frontmatter link is
5
+ * written as a bare address — but `[[address]]` is what an author reaches for,
6
+ * and Obsidian wrote that form for years. Accepting both costs one regex and
7
+ * removes a failure whose message would have to explain the difference.
8
+ *
9
+ * @param {string|null} value - As authored.
10
+ * @returns {string|null} The bare address.
11
+ */
12
+ export function bareAddress(value: string | null): string | null;
13
+ /**
14
+ * The canonical address of a folder note in this package.
15
+ *
16
+ * @param {string} pkg - The content package.
17
+ * @param {string} shortcode - The folder's shortcode.
18
+ * @returns {string} `<pkg>-none-folder-<shortcode>`.
19
+ */
20
+ export function folderAddress(pkg: string, shortcode: string): string;
21
+ /**
22
+ * Collect every folder note in a content tree.
23
+ *
24
+ * The walk is the caller's to supply, so this stays testable without a tree on
25
+ * disk and so one build cannot disagree with another about what the corpus is
26
+ * (#243).
27
+ *
28
+ * @param {Iterable<{frontmatter: object|null, absPath: string}>} notes - As
29
+ * yielded by `walkMarkdownTree`.
30
+ * @param {string} pkg - The content package, for the canonical address.
31
+ * @returns {FolderNote[]} One record per folder note, in walk order.
32
+ * @throws {Error} When a folder note is unusable on its own terms — no
33
+ * shortcode, a shortcode that is not an address segment, or no name.
34
+ */
35
+ export function collectFolderNotes(notes: Iterable<{
36
+ frontmatter: object | null;
37
+ absPath: string;
38
+ }>, pkg: string): FolderNote[];
39
+ /**
40
+ * Index folder notes by every form an author may address one by, and check the
41
+ * invariants that make the index sound.
42
+ *
43
+ * Three keys per folder, and no more: the canonical address, the
44
+ * `folder-<shortcode>` short form, and the bare shortcode. They are the
45
+ * suffixes of the canonical address the grammar admits (#273) — a `packFolder`
46
+ * or `parent` field supplies the type itself, so a bare shortcode is a complete
47
+ * address there.
48
+ *
49
+ * @param {FolderNote[]} folders - From {@link collectFolderNotes}.
50
+ * @returns {{byKey: Map<string, FolderNote>, folders: FolderNote[],
51
+ * resolve: (value: string) => FolderNote, ancestorsOf: (folder: FolderNote)
52
+ * => FolderNote[], parentOf: (folder: FolderNote) => FolderNote|null}}
53
+ * @throws {Error} On a duplicate shortcode, a duplicate id, a dead `parent`,
54
+ * or a parent cycle.
55
+ */
56
+ export function buildFolderNoteIndex(folders: FolderNote[]): {
57
+ byKey: Map<string, FolderNote>;
58
+ folders: FolderNote[];
59
+ resolve: (value: string) => FolderNote;
60
+ ancestorsOf: (folder: FolderNote) => FolderNote[];
61
+ parentOf: (folder: FolderNote) => FolderNote | null;
62
+ };
63
+ /**
64
+ * The Foundry `Folder` document one folder note compiles to, for one pack.
65
+ *
66
+ * The same folder materialises in several packs — the items pack and the
67
+ * journals pack both hold it when both hold something filed in it — and each
68
+ * copy differs only in `type`, which is the document class the folder holds.
69
+ * The `_id` is shared deliberately: a documentation journal filed beside its
70
+ * item is the whole point, and two ids would put them in two folders that
71
+ * merely looked alike.
72
+ *
73
+ * @param {FolderNote} folder - The folder note.
74
+ * @param {FolderNote|null} parent - Its parent, already resolved.
75
+ * @param {string} documentType - `"Item"`, `"JournalEntry"`, …
76
+ * @param {object} stats - The `_stats` block every emitted document carries.
77
+ * @returns {object} The Folder document.
78
+ */
79
+ export function folderDocument(folder: FolderNote, parent: FolderNote | null, documentType: string, stats: object): object;
80
+ /**
81
+ * The note type a folder is authored as.
82
+ *
83
+ * @type {string}
84
+ */
85
+ export const FOLDER_TYPE: string;
86
+ /**
87
+ * The id namespace a derived folder id is hashed under.
88
+ *
89
+ * Distinct from every other document's namespace so a folder and an item
90
+ * sharing a shortcode cannot derive the same id — the collision would be
91
+ * silent, since Foundry keys folders and documents in separate collections and
92
+ * neither would complain.
93
+ *
94
+ * @type {string}
95
+ */
96
+ export const FOLDER_ID_NAMESPACE: string;
97
+ /**
98
+ * One folder note, read from the tree.
99
+ *
100
+ * @typedef {object} FolderNote
101
+ * @property {string} shortcode - Its `(type, shortcode)` identity.
102
+ * @property {string} address - The canonical `<pkg>-none-folder-<shortcode>`.
103
+ * @property {string} name - The display name.
104
+ * @property {string|null} color - CSS hex, or `null`.
105
+ * @property {Record<string, string|null>} parent - The parent's authored
106
+ * address per pack, keyed by pack name with {@link DEFAULT_PARENT} for the
107
+ * unstated case. A folder's identity is one thing; its hierarchy is per-pack.
108
+ * @property {string} id - The Foundry id: authored, or derived from `address`.
109
+ * @property {boolean} derivedId - Whether `id` was derived rather than authored.
110
+ * @property {string} absPath - The file it was read from, for diagnostics.
111
+ */
112
+ /**
113
+ * The key a per-pack `parent` map uses for "everywhere else".
114
+ *
115
+ * Spelled out rather than left as the absence of a key, so a map that states
116
+ * only exceptions still reads as a complete answer.
117
+ *
118
+ * @type {string}
119
+ */
120
+ export const DEFAULT_PARENT: string;
121
+ /**
122
+ * One folder note, read from the tree.
123
+ */
124
+ export type FolderNote = {
125
+ /**
126
+ * - Its `(type, shortcode)` identity.
127
+ */
128
+ shortcode: string;
129
+ /**
130
+ * - The canonical `<pkg>-none-folder-<shortcode>`.
131
+ */
132
+ address: string;
133
+ /**
134
+ * - The display name.
135
+ */
136
+ name: string;
137
+ /**
138
+ * - CSS hex, or `null`.
139
+ */
140
+ color: string | null;
141
+ /**
142
+ * - The parent's authored
143
+ * address per pack, keyed by pack name with {@link DEFAULT_PARENT} for the
144
+ * unstated case. A folder's identity is one thing; its hierarchy is per-pack.
145
+ */
146
+ parent: Record<string, string | null>;
147
+ /**
148
+ * - The Foundry id: authored, or derived from `address`.
149
+ */
150
+ id: string;
151
+ /**
152
+ * - Whether `id` was derived rather than authored.
153
+ */
154
+ derivedId: boolean;
155
+ /**
156
+ * - The file it was read from, for diagnostics.
157
+ */
158
+ absPath: string;
159
+ };
@@ -92,6 +92,59 @@ export function fetchCatalog(config: object, rel: {
92
92
  export function fetchCatalogFromPath(config: object, rel: {
93
93
  id: string;
94
94
  }, source: string): Promise<string>;
95
+ /**
96
+ * Fetch one dependency's published content index (#239).
97
+ *
98
+ * **The chain is entirely declared.** The relationship names the dependency's
99
+ * manifest, the manifest advertises `flags.metadataUrl`, and that URL is the
100
+ * index — so nothing here holds an address of its own, and a dependency that
101
+ * moves its release assets does not break its consumers.
102
+ *
103
+ * Pinned by the same rule as the catalogue: `compatibility.verified` is the
104
+ * version this repository was built against, so a floating `releases/latest`
105
+ * URL is rewritten to it. A consumer resolving addresses against whatever the
106
+ * dependency published this morning is not reproducible.
107
+ *
108
+ * Idempotent: a complete cache for the resolved version is left alone.
109
+ *
110
+ * @param {object} config - The resolved build configuration.
111
+ * @param {{id: string, manifest: string, verified?: string}} rel - The declared
112
+ * relationship.
113
+ * @returns {Promise<string>} The cached index file.
114
+ */
115
+ export function fetchMetadata(config: object, rel: {
116
+ id: string;
117
+ manifest: string;
118
+ verified?: string;
119
+ }): Promise<string>;
120
+ /**
121
+ * Fill the index cache from a locally built artifact rather than a release.
122
+ *
123
+ * The counterpart of {@link fetchCatalogFromPath}, and the same escape hatch
124
+ * for the same reason: two packages being changed together cannot each wait for
125
+ * the other to ship. The index is looked for beside the manifest — which is
126
+ * where a build leaves it and where the release publishes it — so a package
127
+ * directory and an unpacked zip are both usable as-is.
128
+ *
129
+ * @param {object} config - The resolved build configuration.
130
+ * @param {{id: string}} rel - The declared relationship.
131
+ * @param {string} source - Path to the artifact or its directory.
132
+ * @returns {Promise<string>} The cached index file.
133
+ */
134
+ export function fetchMetadataFromPath(config: object, rel: {
135
+ id: string;
136
+ }, source: string): Promise<string>;
137
+ /**
138
+ * Fetch every declared dependency's content index.
139
+ *
140
+ * A wider set than {@link fetchAllCatalogs}: an index is fetched for *every*
141
+ * dependency, a catalogue only for those declaring `itemCatalog: true`. See
142
+ * {@link metadataRelationships} for why the two sets differ.
143
+ *
144
+ * @param {object} config - The resolved build configuration.
145
+ * @returns {Promise<number>} How many indexes were fetched.
146
+ */
147
+ export function fetchAllMetadata(config: object): Promise<number>;
95
148
  /**
96
149
  * Fetch every opted-in dependency. The `deps fetch` command.
97
150
  *
@@ -55,7 +55,7 @@ export function entriesForNote(fm: object, name: string, address: string, body:
55
55
  * @returns {{entries: Array<object>, notes: number,
56
56
  * skipped: Array<{file: string, reason: string}>}}
57
57
  */
58
- export function collectManifestEntries(contentBase: string, ctx: object): {
58
+ export function collectFoundryEntries(contentBase: string, ctx: object): {
59
59
  entries: Array<object>;
60
60
  notes: number;
61
61
  skipped: Array<{
@@ -74,7 +74,7 @@ export function collectManifestEntries(contentBase: string, ctx: object): {
74
74
  * @returns {{contentPackage: string, foundryPackageId: string, packRouter: object,
75
75
  * web: boolean, skipDirectories: readonly string[]}}
76
76
  */
77
- export function manifestContext(config?: object): {
77
+ export function foundryIdentities(config?: object): {
78
78
  contentPackage: string;
79
79
  foundryPackageId: string;
80
80
  packRouter: object;
@@ -82,45 +82,23 @@ export function manifestContext(config?: object): {
82
82
  skipDirectories: readonly string[];
83
83
  };
84
84
  /**
85
- * Emits this package's link manifest.
85
+ * The identities an emission runs against, from configuration.
86
86
  *
87
- * One package, because a configuration declares exactly one `contentPackage`
88
- * and nothing in the surface can express a second. {@link writeManifests} keeps
89
- * its package→entries map it is the general writer but there is no setting
90
- * here to choose with.
87
+ * {@link foundryIdentities} plus what only a *manifest* emission needs. The
88
+ * split is what lets the content index derive the same Foundry addresses from
89
+ * the same code without also depending on whether the package publishes pages,
90
+ * which is no part of a UUID (#239).
91
91
  *
92
- * @param {object} [options] - Options.
93
- * @param {string} [options.contentBase] - The content tree; defaults to the
94
- * configured `paths.content`.
95
- * @param {string} [options.outDir] - Where to write; defaults to the configured
96
- * `paths.manifestOut`.
97
- * @param {object} [options.config] - A resolved configuration; loaded when
98
- * omitted.
99
- * @returns {{written: Array<{package: string, file: string, count: number}>,
100
- * entries: number, notes: number,
101
- * skipped: Array<{file: string, reason: string}>}}
102
- * @throws {Error} When the repository does not declare that it publishes a
103
- * manifest, when the tree is absent, or when it yields no published note — a
104
- * manifest claiming this package publishes nothing is worse than none, since
105
- * a consumer reads it as authoritative and turns every link into this package
106
- * into a reported typo.
92
+ * @param {object} [config] - A resolved configuration; loaded when omitted.
93
+ * @returns {{contentPackage: string, foundryPackageId: string, packRouter: object,
94
+ * web: boolean, skipDirectories: readonly string[]}}
107
95
  */
108
- export function emitLinkManifest({ contentBase, outDir, config }?: {
109
- contentBase?: string | undefined;
110
- outDir?: string | undefined;
111
- config?: object | undefined;
112
- }): {
113
- written: Array<{
114
- package: string;
115
- file: string;
116
- count: number;
117
- }>;
118
- entries: number;
119
- notes: number;
120
- skipped: Array<{
121
- file: string;
122
- reason: string;
123
- }>;
96
+ export function entryContext(config?: object): {
97
+ contentPackage: string;
98
+ foundryPackageId: string;
99
+ packRouter: object;
100
+ web: boolean;
101
+ skipDirectories: readonly string[];
124
102
  };
125
103
  /**
126
104
  * The reserved anchor name for a journal's **first** page.
@@ -29,9 +29,13 @@ export function matchesKind(value: unknown, kind: string): boolean;
29
29
  * @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
30
30
  * The system blocks to check, and what each accepts. See
31
31
  * {@link DEFAULT_SYSTEM_BLOCKS}.
32
+ * @param {readonly string[]} [opts.packs] - The pack names this package
33
+ * declares, for a `data:` field whose map is keyed by pack. Supplied by the
34
+ * caller like `schemas` and `vocabulary`, and absent it no claim is made
35
+ * about those keys.
32
36
  * @returns {object[]} Findings, each with a locator where one is obtainable.
33
37
  */
34
- export function lintNote(note: object, { schemas, index, vocabulary, systems }: {
38
+ export function lintNote(note: object, { schemas, index, vocabulary, packs, systems }: {
35
39
  schemas: Record<string, readonly object[]>;
36
40
  index?: object | undefined;
37
41
  vocabulary?: Record<string, object> | undefined;
@@ -39,6 +43,7 @@ export function lintNote(note: object, { schemas, index, vocabulary, systems }:
39
43
  known?: readonly string[];
40
44
  fieldVocabulary?: boolean;
41
45
  }>> | undefined;
46
+ packs?: readonly string[] | undefined;
42
47
  }): object[];
43
48
  /**
44
49
  * Check every note in a built index against its type's schema.
@@ -51,10 +56,12 @@ export function lintNote(note: object, { schemas, index, vocabulary, systems }:
51
56
  * @param {boolean} [opts.references=true] - Whether to check references.
52
57
  * @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
53
58
  * The system blocks to check. See {@link DEFAULT_SYSTEM_BLOCKS}.
59
+ * @param {readonly string[]} [opts.packs] - The declared pack names; see
60
+ * {@link lintNote}.
54
61
  * @returns {{findings: object[], notes: number}} The findings, and how many
55
62
  * notes were inspected.
56
63
  */
57
- export function lintFrontmatter(index: object, { schemas, vocabulary, references, systems }: {
64
+ export function lintFrontmatter(index: object, { schemas, vocabulary, packs, references, systems }: {
58
65
  schemas: Record<string, readonly object[]>;
59
66
  vocabulary?: Record<string, object> | undefined;
60
67
  references?: boolean | undefined;
@@ -62,6 +69,7 @@ export function lintFrontmatter(index: object, { schemas, vocabulary, references
62
69
  known?: readonly string[];
63
70
  fieldVocabulary?: boolean;
64
71
  }>> | undefined;
72
+ packs?: readonly string[] | undefined;
65
73
  }): {
66
74
  findings: object[];
67
75
  notes: number;
@@ -9,6 +9,40 @@ export function getFrontmatter(fm: any, key: any, defaultValue?: undefined): any
9
9
  * Falls back to top-level `fm[key]` if `sohl` doesn't carry the key.
10
10
  */
11
11
  export function sohlField(fm: any, key: any, defaultValue?: undefined): any;
12
+ /**
13
+ * Read a `sohl:` field, seeing the **destination** position as well (#126).
14
+ *
15
+ * Five declarations resolve their value by re-reading the note rather than by
16
+ * taking the one {@link module:engine/system-block.resolveFieldValue} handed
17
+ * them — `subType`, `charges`, a mystery's `skillAptitudes`, an affiliation's
18
+ * `relations` and a projectile's impact die — because each validates a *shape
19
+ * spread over several keys* rather than coercing one scalar.
20
+ *
21
+ * That was equivalent while every note authored inside the block, and stopped
22
+ * being so once a note may author at `sohl.system.<to>` instead: {@link
23
+ * sohlField} sees `sohl.<key>` and the top level, never inside `sohl.system`.
24
+ * So those five read as unset and shipped their empty value — a missing
25
+ * `subType` is a thrown build error, and the other four ship empty in silence,
26
+ * which is the failure class the passthrough exists to prevent.
27
+ *
28
+ * The **destination wins**, matching every other field's resolution order.
29
+ *
30
+ * `legacyKey` is for the one pair whose two positions are spelled differently:
31
+ * a projectile authors `impact.die` and stores `impactBase.die`, so the reader
32
+ * has to be told both. It is the same split `FieldSpec.name`/`legacyKey` makes
33
+ * (#305), for the same reason — one name cannot key two positions.
34
+ *
35
+ * @param {object} fm - The note's frontmatter.
36
+ * @param {string} to - The key at the destination, dotted for a nested one.
37
+ * @param {any} [defaultValue] - What an unauthored field reads as.
38
+ * @param {object} [options] - Options.
39
+ * @param {string} [options.legacyKey] - The key the block still carries, when
40
+ * it is not spelled `to`. Defaults to `to`.
41
+ * @returns {any} The value.
42
+ */
43
+ export function sohlSystemField(fm: object, to: string, defaultValue?: any, { legacyKey }?: {
44
+ legacyKey?: string | undefined;
45
+ }): any;
12
46
  /**
13
47
  * Resolve the `charges` block shared by Mystery and Mystical Ability items.
14
48
  *
@@ -97,3 +131,33 @@ export function parseValueDesc(raw: any): {
97
131
  label: string;
98
132
  maxValue: number;
99
133
  }[];
134
+ /**
135
+ * The compendium folder a note names, and how it named it.
136
+ *
137
+ * Two spellings, deliberately not merged into one value: `packFolder:` is a
138
+ * folder note's **address** (`folder-poisonsandtoxins`) and `folder:` is a
139
+ * Foundry **id** (`ONXsqZAIZr2qzxTb`). Which one a value is cannot be told from
140
+ * the string — both are alphanumeric — so the field it was written in is what
141
+ * says, and that answer is carried rather than re-derived (#251).
142
+ *
143
+ * **`packFolder` was a path for one release** (`Possessions/Misc_Gear/Cooking`)
144
+ * and is an address now (#255). A path encoded the hierarchy *in the value*, so
145
+ * reparenting a folder made every note naming it wrong — a structural edit
146
+ * became a corpus-wide rewrite. An address is stable under reparenting, which
147
+ * is exactly why a note is addressed by `(type, shortcode)` and never by
148
+ * `file.path`. The path form is **removed rather than deprecated**: it had no
149
+ * authors to migrate, which is the whole reason the change was cheap enough to
150
+ * make.
151
+ *
152
+ * `packFolder` wins where both are present. Nothing about `folder` changes: a
153
+ * note that names one is read, resolved and emitted exactly as before, until
154
+ * #260 retires it.
155
+ *
156
+ * @param {object|null|undefined} fm - Parsed frontmatter.
157
+ * @returns {{value: string|null, isAddress: boolean}} The authored value, and
158
+ * whether it is a folder note's address.
159
+ */
160
+ export function folderField(fm: object | null | undefined): {
161
+ value: string | null;
162
+ isAddress: boolean;
163
+ };
@@ -1,3 +1,13 @@
1
+ /**
2
+ * The compiler class a pack of one document type and one system gets.
3
+ *
4
+ * @param {string} docType - The Foundry document type the pack holds.
5
+ * @param {string|null} [system] - The system the pack declares, if any.
6
+ * @returns {Function|undefined} The compiler class, or `undefined` for a
7
+ * document type nothing here compiles — which {@link generatePack} reports
8
+ * rather than defaulting past.
9
+ */
10
+ export function compilerFor(docType: string, system?: string | null): Function | undefined;
1
11
  /**
2
12
  * The generated JSON of **every** configured Item pack — what the actors pass
3
13
  * reads its predefined items from.
@@ -31,6 +41,34 @@
31
41
  * `(type, shortcode)` instead, naming the being (#49).
32
42
  */
33
43
  export function itemPackJsonDirs(config?: object, system?: string | null): string[];
44
+ /**
45
+ * The compiled JSON a bundle may hold copies of, by document type.
46
+ *
47
+ * An `Adventure` carries **copies**, not references, so a bundle resolves its
48
+ * `contents` against compiled output rather than against the content tree — the
49
+ * same arrangement the actors pass has for `itemsSourceDirs`, generalised to
50
+ * every document class an Adventure can hold (#259).
51
+ *
52
+ * Two kinds of pack are left out, each because it holds nothing a note
53
+ * addresses. A **prebuilt** pack's JSON is checked in rather than compiled, so
54
+ * no note is routed into it and nothing in it answers to an address. An
55
+ * **Adventure** pack holds Adventures, and Foundry's `contentFields` has no
56
+ * field for one — a bundle of bundles is not a shape the document admits.
57
+ *
58
+ * **Scoped to one system when the pack has one**, exactly as
59
+ * {@link itemPackJsonDirs} is: a pack declaring `system: sohl` reads that
60
+ * system's packs and the system-neutral ones, so a `(type, shortcode)` that
61
+ * exists in two systems is read out of the right catalogue. Asking for no
62
+ * system reads them all, which is every single-system build.
63
+ *
64
+ * @param {object} [config] - The resolved build configuration. Defaults to this
65
+ * repository's.
66
+ * @param {string|null} [system] - The system whose documents are wanted.
67
+ * Omitted or `null`, every pack is read.
68
+ * @returns {Record<string, string[]>} Each readable pack's JSON directory, by
69
+ * the Foundry document type it holds.
70
+ */
71
+ export function bundleSourceJsonDirs(config?: object, system?: string | null): Record<string, string[]>;
34
72
  /**
35
73
  * The passes to run, ordered so that each one follows the output it reads.
36
74
  *
@@ -41,10 +41,45 @@ export function parseMarkdownFile(filePath: any): {
41
41
  * @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore.
42
42
  * Defaults to the configured list.
43
43
  */
44
- export function walkMarkdownTree(rootDir: string, { skipDirectories }?: {
45
- skipDirectories?: readonly string[] | undefined;
46
- }): Generator<{
47
- file: string & NonSharedBuffer;
44
+ /**
45
+ * Refuse a corpus read whose scope its caller did not state.
46
+ *
47
+ * The rule of #243 in one place, so every reader of the tree refuses the same
48
+ * way and says so in the same words. It is shared rather than repeated because
49
+ * the corpus is no longer read only by {@link walkMarkdownTree}: a pass that
50
+ * reads the content index instead is making the identical claim about which
51
+ * files it is looking at, and must be held to the identical requirement — a
52
+ * scope that quietly defaulted there would reintroduce exactly the second
53
+ * answer the walk's requirement removed.
54
+ *
55
+ * @param {readonly string[]|undefined} skipDirectories - The stated scope.
56
+ * @param {string} who - The reader, named in the message.
57
+ * @throws {Error} When no scope was stated.
58
+ * @returns {void}
59
+ */
60
+ export function assertStatedScope(skipDirectories: readonly string[] | undefined, who: string): void;
61
+ /**
62
+ * Refuse a corpus read whose records its caller did not supply.
63
+ *
64
+ * The sibling of {@link assertStatedScope}, and required for the same reason
65
+ * one step further on. These two readers cannot derive the corpus themselves:
66
+ * deriving it reaches the pack router and the manifest emitter, which reach the
67
+ * compilers, which load this module — so importing the index here closes a
68
+ * cycle. They take the records their caller already holds.
69
+ *
70
+ * That is not a workaround dressed up as a rule. A compile runs several passes
71
+ * over one tree, and the whole of #243 is that they must not each answer "which
72
+ * files are the corpus?" for themselves. Requiring the answer to be handed in
73
+ * makes the sharing structural rather than remembered.
74
+ *
75
+ * @param {readonly object[]|undefined} records - The supplied corpus.
76
+ * @param {string} who - The reader, named in the message.
77
+ * @throws {Error} When no corpus was supplied.
78
+ * @returns {void}
79
+ */
80
+ export function assertSuppliedCorpus(records: readonly object[] | undefined, who: string): void;
81
+ export function walkMarkdownTree(rootDir: any, { skipDirectories }?: {}): Generator<{
82
+ file: string;
48
83
  absPath: string;
49
84
  frontmatter: null;
50
85
  body: string;
@@ -52,7 +87,7 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories }?: {
52
87
  bodyLine?: undefined;
53
88
  bodyColumn?: undefined;
54
89
  } | {
55
- file: string & NonSharedBuffer;
90
+ file: string;
56
91
  absPath: string;
57
92
  frontmatter: any;
58
93
  body: string;
@@ -61,45 +96,74 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories }?: {
61
96
  bodyColumn: number;
62
97
  }, void, unknown>;
63
98
  /**
64
- * Resolve the required `sohl.archetype` frontmatter for an Item/Actor entry
99
+ * The template priority a note states, for a system that treats an unstated one
100
+ * as "not a template" rather than as an authoring error (#266).
101
+ *
102
+ * Reads exactly the positions {@link resolveTemplatePriority} reads, including
103
+ * the retiring `archetype` spelling, and refuses the same contradiction — so
104
+ * the two systems cannot disagree about what a note said. It differs only in
105
+ * what silence means: SoHL requires the statement, while HM3 keeps the value in
106
+ * a flag it simply omits, so there is no tri-state for an absent value to
107
+ * corrupt and nothing to demand.
108
+ *
109
+ * @param {object} fm Parsed frontmatter.
110
+ * @param {string} label Human-readable context for error messages.
111
+ * @param {object} [options] Options.
112
+ * @param {string} [options.block="sohl"] The system block being compiled.
113
+ * @returns {number|null} The priority, or `null` when the note is not a
114
+ * template or states nothing.
115
+ * @throws {Error} When both spellings are present and disagree.
116
+ */
117
+ export function statedTemplatePriority(fm: object, label: string, { block }?: {
118
+ block?: string | undefined;
119
+ }): number | null;
120
+ /**
121
+ * Resolve the required `templatePriority` frontmatter for an Item/Actor entry
65
122
  * (the archetype contract, #604). The property is a nullable number that
66
123
  * authors must state explicitly:
67
- * - a number → the document is an archetype of that priority.
68
- * - `null` → the document is not an archetype.
69
- * - absent → an authoring error (throws), so "not an archetype" is never
124
+ * - a number → the document is a template of that priority.
125
+ * - `null` → the document is not a template.
126
+ * - absent → an authoring error (throws), so "not a template" is never
70
127
  * silently assumed.
71
128
  *
72
- * Reads `sohl.archetype`, falling back to a top-level `archetype` key to match
73
- * {@link sohlField}'s nested-then-top-level resolution.
129
+ * Reads the positions {@link findTemplatePriority} lists: `data.templatePriority`
130
+ * first the specified home — then the system block and the top level, and
131
+ * finally the retiring `archetype` spelling in the same two places (#266).
74
132
  *
75
133
  * @param {object} fm Parsed frontmatter.
76
134
  * @param {string} label Human-readable context for error messages.
77
- * @returns {number|undefined} The archetype priority, or `undefined` when null.
78
- * @throws {Error} When `sohl.archetype` is absent or is not a number/null.
135
+ * @param {object} [options] Options.
136
+ * @param {string} [options.block="sohl"] The system block being compiled.
137
+ * @returns {number|undefined} The template priority, or `undefined` when null.
138
+ * @throws {Error} When the property is absent, is not a number/null, or both
139
+ * spellings are present and disagree.
79
140
  */
80
- export function resolveArchetype(fm: object, label: string): number | undefined;
141
+ export function resolveTemplatePriority(fm: object, label: string, { block }?: {
142
+ block?: string | undefined;
143
+ }): number | undefined;
81
144
  /**
82
- * The value a document's `system.archetype` carries, from the required
83
- * `sohl.archetype` frontmatter (#126, sohl#1780).
145
+ * The value a document's `system.templatePriority` carries, from the required
146
+ * `templatePriority` frontmatter (#126, sohl#1780, renamed off `archetype` by
147
+ * #266 / sohl#1836).
84
148
  *
85
149
  * A **schema field**, so the tri-state is written out in full rather than
86
- * expressed by a key's presence: a number is an archetype at that priority,
87
- * and `null` is not an archetype. This is where {@link resolveArchetype}'s
150
+ * expressed by a key's presence: a number is a template at that priority, and
151
+ * `null` is not a template. This is where {@link resolveTemplatePriority}'s
88
152
  * `undefined` becomes the field's `null` — an emitted `undefined` would be
89
153
  * dropped by `JSON.stringify`, leaving the compiled document with no
90
- * `archetype` at all and the tri-state readable as two.
154
+ * `templatePriority` at all and the tri-state readable as two.
91
155
  *
92
- * **`0` is an archetype.** It is the priority SoHL's own archetypes ship at,
93
- * and it is falsy, so this returns it unchanged and every caller must ask
156
+ * **`0` is a template.** It is the priority SoHL's own templates ship at, and
157
+ * it is falsy, so this returns it unchanged and every caller must ask
94
158
  * `typeof v === "number"` rather than testing truthiness.
95
159
  *
96
160
  * @param {object} fm Parsed frontmatter.
97
161
  * @param {string} label Human-readable context for error messages.
98
- * @returns {number|null} The archetype priority, or `null` for a document
99
- * that is not an archetype.
100
- * @throws {Error} When `sohl.archetype` is absent or invalid.
162
+ * @returns {number|null} The template priority, or `null` for a document that
163
+ * is not a template.
164
+ * @throws {Error} When the property is absent or invalid.
101
165
  */
102
- export function systemArchetype(fm: object, label: string): number | null;
166
+ export function systemTemplatePriority(fm: object, label: string): number | null;
103
167
  /**
104
168
  * Generates a compendium-source filename: `Name_id.json` with non-
105
169
  * alphanumeric runs replaced by underscores.
@@ -280,7 +344,7 @@ export function defaultStats(): object;
280
344
  * this repository's own.
281
345
  * @returns {{byShortcode: Map, types: Set}} From `buildWikilinkIndex`.
282
346
  */
283
- export function buildContentLinkIndex(contentBase: string, router?: object): {
347
+ export function buildContentLinkIndex(contentBase: string, router?: object, { skipDirectories, config, records, problems }?: {}): {
284
348
  byShortcode: Map<any, any>;
285
349
  types: Set<any>;
286
350
  };
@@ -320,7 +384,7 @@ export function convertNoteWikilinks(body: string, { type, id, pack, docPack, in
320
384
  * @returns {Array<{fm: object, path: string, tld: string, folder: string,
321
385
  * absPath: string}>}
322
386
  */
323
- export function collectContentDocs(contentBase: string): Array<{
387
+ export function collectContentDocs(contentBase: string, { skipDirectories, config, records, problems }?: {}): Array<{
324
388
  fm: object;
325
389
  path: string;
326
390
  tld: string;
@@ -352,7 +416,7 @@ export function collectContentDocs(contentBase: string): Array<{
352
416
  * compile rather than shipping a table-shaped hole. The error carries
353
417
  * `position`, the directive's own line.
354
418
  */
355
- export function expandNoteTables(body: string, { docs, name, fm, bodyLine }: {
419
+ export function expandNoteTables(body: string, { docs, name, fm, bodyLine, sqlTables }: {
356
420
  docs: Array<object>;
357
421
  name: string;
358
422
  fm?: object | undefined;
@@ -384,7 +448,7 @@ export function loadFolders(foldersFile: any): any[];
384
448
  * Returns { resolver, folders } where folders is the validated list.
385
449
  */
386
450
  export function buildFolderResolver(folders: any): {
387
- resolver: (folderId: any) => string | null;
451
+ resolver: (value: string | null | undefined) => string | null;
388
452
  folders: any;
389
453
  };
390
454
  /**
@@ -402,4 +466,4 @@ export function writeFolderDocs(folders: any, stats: any, destDir: any, document
402
466
  export const md: import("markdown-it").MarkdownIt;
403
467
  export { slugify } from "./content-slug.mjs";
404
468
  export { makeId } from "./ids.mjs";
405
- export { getFrontmatter, sohlField, resolveCharges, resolveSkillAptitudes, resolveRelation, requireSubType, parseValueDesc } from "./frontmatter.mjs";
469
+ export { getFrontmatter, sohlField, folderField, resolveCharges, resolveSkillAptitudes, resolveRelation, requireSubType, parseValueDesc } from "./frontmatter.mjs";