@heroiclands/package-build 20.3.1 → 20.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +351 -0
- package/CONTENT.md +322 -110
- package/README.md +21 -10
- package/bin/content-build.mjs +204 -92
- package/bin/package-build.mjs +48 -15
- package/config.mjs +6 -8
- package/content-config.mjs +518 -151
- package/docs/api.md +1352 -0
- package/docs/commands.md +1609 -0
- package/docs/configuration.md +1432 -0
- package/docs/content-format.md +73 -74
- package/docs/diagnostics.md +356 -0
- package/docs/getting-started.md +813 -0
- package/docs/project-setup.md +469 -0
- package/e2e.mjs +4 -4
- package/engine/actor-compiler.mjs +64 -61
- package/engine/address-charset.mjs +6 -6
- package/engine/address-diff.mjs +57 -52
- package/engine/anchored-sections.mjs +1 -1
- package/engine/anchors.mjs +1 -1
- package/engine/base-compiler.mjs +42 -38
- package/engine/bundle-notes.mjs +4 -4
- package/engine/bundles.mjs +17 -8
- package/engine/code-fences.mjs +3 -3
- package/engine/compendiums.mjs +8 -8
- package/engine/compile-corpus.mjs +4 -5
- package/engine/content-address.mjs +24 -24
- package/engine/content-charset.mjs +1 -1
- package/engine/content-format-check.mjs +24 -25
- package/engine/content-format.mjs +8 -8
- package/engine/content-html.mjs +202 -0
- package/engine/content-icons.mjs +196 -165
- package/engine/content-index.mjs +61 -40
- package/engine/content-links.mjs +74 -41
- package/engine/content-lint.mjs +64 -34
- package/engine/content-package.mjs +7 -9
- package/engine/content-slug.mjs +2 -2
- package/engine/content-tables.mjs +38 -34
- package/engine/content-tree.mjs +1 -1
- package/engine/derived-fields.mjs +174 -0
- package/engine/diagnostics.mjs +3 -4
- package/engine/document-subtypes.mjs +6 -7
- package/engine/field-reference.mjs +8 -8
- package/engine/field-spec.mjs +29 -30
- package/engine/folder-notes.mjs +18 -16
- package/engine/foreign-catalog.mjs +8 -9
- package/engine/foundry-entries.mjs +19 -20
- package/engine/frontmatter-lint.mjs +95 -103
- package/engine/frontmatter.mjs +11 -11
- package/engine/generate.mjs +44 -39
- package/engine/helpers.mjs +147 -84
- package/engine/homepage.mjs +20 -22
- package/engine/ids.mjs +15 -15
- package/engine/index-records.mjs +4 -4
- package/engine/index.mjs +26 -10
- package/engine/item-compiler.mjs +54 -29
- package/engine/item-docs.mjs +11 -13
- package/engine/item-registry.mjs +13 -13
- package/engine/journals.mjs +24 -15
- package/engine/macros.mjs +3 -3
- package/engine/map-notes.mjs +6 -6
- package/engine/metadata-index.mjs +5 -5
- package/engine/note-claims.mjs +51 -41
- package/engine/note-ids.mjs +5 -5
- package/engine/note-package.mjs +4 -4
- package/engine/note-renames.mjs +3 -3
- package/engine/note-schemas.mjs +3 -8
- package/engine/note-vocabulary.mjs +56 -54
- package/engine/pack-config.mjs +39 -25
- package/engine/pack-router.mjs +0 -0
- package/engine/pdf-build.mjs +464 -0
- package/engine/pdf-fonts.mjs +420 -0
- package/engine/pdf-render.mjs +798 -0
- package/engine/pdf-toc.mjs +525 -0
- package/engine/prose-config.mjs +9 -9
- package/engine/prose-lint.mjs +12 -13
- package/engine/region-events.mjs +2 -2
- package/engine/retired-fields.mjs +24 -22
- package/engine/runtime-only-fields.mjs +1 -1
- package/engine/scene-levels.mjs +1 -1
- package/engine/scenes.mjs +33 -25
- package/engine/schema-check.mjs +21 -21
- package/engine/schema-extract.mjs +1 -1
- package/engine/site-build.mjs +66 -50
- package/engine/site-index.mjs +17 -18
- package/engine/sql-tables.mjs +21 -13
- package/engine/subtype-registry.mjs +5 -4
- package/engine/system-block.mjs +18 -19
- package/engine/systems.mjs +2 -2
- package/engine/web-wikilinks.mjs +41 -38
- package/engine/wikilink-syntax.mjs +16 -16
- package/engine/wikilinks.mjs +40 -42
- package/hm3/actors.mjs +30 -22
- package/hm3/document-subtypes.mjs +5 -5
- package/hm3/index.mjs +2 -2
- package/hm3/item-builders.mjs +1 -1
- package/hm3/item-fields.mjs +3 -3
- package/hm3/items.mjs +28 -8
- package/hm3/template-priority.mjs +2 -2
- package/lang.mjs +3 -3
- package/manifest.mjs +17 -20
- package/package.json +1 -2
- package/release.mjs +66 -6
- package/sohl/actors.mjs +36 -28
- package/sohl/affiliation-standings.mjs +2 -2
- package/sohl/being-info.mjs +5 -5
- package/sohl/default-item-art.mjs +5 -5
- package/sohl/document-subtypes.mjs +5 -5
- package/sohl/index.mjs +3 -3
- package/sohl/item-builders.mjs +7 -7
- package/sohl/item-fields.mjs +8 -9
- package/sohl/items.mjs +28 -6
- package/sohl/kb-passes.mjs +5 -5
- package/sohl/note-schemas.mjs +12 -12
- package/sohl/skill-base.mjs +1 -1
- package/types/content-config.d.mts +91 -39
- package/types/e2e.d.mts +3 -3
- package/types/engine/actor-compiler.d.mts +45 -40
- package/types/engine/address-charset.d.mts +6 -6
- package/types/engine/address-diff.d.mts +60 -6
- package/types/engine/base-compiler.d.mts +27 -21
- package/types/engine/bundle-notes.d.mts +3 -3
- package/types/engine/bundles.d.mts +10 -1
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/compendiums.d.mts +3 -3
- package/types/engine/compile-corpus.d.mts +1 -1
- package/types/engine/content-address.d.mts +20 -20
- package/types/engine/content-format-check.d.mts +6 -6
- package/types/engine/content-format.d.mts +2 -2
- package/types/engine/content-html.d.mts +78 -0
- package/types/engine/content-icons.d.mts +139 -92
- package/types/engine/content-index.d.mts +59 -15
- package/types/engine/content-links.d.mts +8 -8
- package/types/engine/content-lint.d.mts +8 -6
- package/types/engine/content-package.d.mts +6 -8
- package/types/engine/content-tables.d.mts +49 -18
- package/types/engine/derived-fields.d.mts +101 -0
- package/types/engine/diagnostics.d.mts +2 -2
- package/types/engine/document-subtypes.d.mts +3 -3
- package/types/engine/field-spec.d.mts +37 -39
- package/types/engine/folder-notes.d.mts +4 -5
- package/types/engine/foreign-catalog.d.mts +3 -3
- package/types/engine/foundry-entries.d.mts +6 -7
- package/types/engine/frontmatter-lint.d.mts +10 -9
- package/types/engine/frontmatter.d.mts +7 -7
- package/types/engine/generate.d.mts +7 -7
- package/types/engine/helpers.d.mts +128 -56
- package/types/engine/homepage.d.mts +16 -18
- package/types/engine/ids.d.mts +13 -13
- package/types/engine/index-records.d.mts +3 -3
- package/types/engine/index.d.mts +6 -0
- package/types/engine/item-compiler.d.mts +21 -5
- package/types/engine/item-docs.d.mts +2 -2
- package/types/engine/item-registry.d.mts +6 -6
- package/types/engine/journals.d.mts +12 -3
- package/types/engine/map-notes.d.mts +2 -2
- package/types/engine/metadata-index.d.mts +4 -4
- package/types/engine/note-claims.d.mts +30 -22
- package/types/engine/note-ids.d.mts +4 -4
- package/types/engine/note-package.d.mts +1 -1
- package/types/engine/note-renames.d.mts +3 -3
- package/types/engine/note-vocabulary.d.mts +31 -203
- package/types/engine/pack-config.d.mts +7 -7
- package/types/engine/pack-router.d.mts +1 -1
- package/types/engine/pdf-build.d.mts +42 -0
- package/types/engine/pdf-fonts.d.mts +30 -0
- package/types/engine/pdf-render.d.mts +144 -0
- package/types/engine/pdf-toc.d.mts +114 -0
- package/types/engine/prose-config.d.mts +9 -9
- package/types/engine/prose-lint.d.mts +3 -4
- package/types/engine/region-events.d.mts +2 -2
- package/types/engine/retired-fields.d.mts +10 -9
- package/types/engine/scenes.d.mts +10 -1
- package/types/engine/schema-check.d.mts +13 -13
- package/types/engine/site-build.d.mts +52 -25
- package/types/engine/site-index.d.mts +3 -4
- package/types/engine/sql-tables.d.mts +11 -5
- package/types/engine/subtype-registry.d.mts +3 -3
- package/types/engine/system-block.d.mts +3 -3
- package/types/engine/web-wikilinks.d.mts +7 -7
- package/types/engine/wikilink-syntax.d.mts +17 -17
- package/types/engine/wikilinks.d.mts +14 -14
- package/types/hm3/actors.d.mts +9 -1
- package/types/hm3/document-subtypes.d.mts +1 -1
- package/types/hm3/items.d.mts +9 -2
- package/types/hm3/template-priority.d.mts +1 -1
- package/types/manifest.d.mts +8 -8
- package/types/release.d.mts +15 -4
- package/types/sohl/actors.d.mts +12 -4
- package/types/sohl/affiliation-standings.d.mts +2 -2
- package/types/sohl/being-info.d.mts +2 -2
- package/types/sohl/document-subtypes.d.mts +1 -1
- package/types/sohl/items.d.mts +9 -2
- package/types/sohl/note-schemas.d.mts +1 -1
- package/MIGRATING.md +0 -608
package/engine/helpers.mjs
CHANGED
|
@@ -43,19 +43,19 @@ import { resolveNoteId } from "./note-ids.mjs";
|
|
|
43
43
|
import { loadForeignIndexes } from "./metadata-index.mjs";
|
|
44
44
|
// The record accessors only — deriving records reaches the pack router and the
|
|
45
45
|
// manifest emitter, which reach the compilers, which load this module. Reading
|
|
46
|
-
// a record needs none of that
|
|
46
|
+
// a record needs none of that.
|
|
47
47
|
import { authoredFrontmatter, isNoteRecord, noteFile } from "./index-records.mjs";
|
|
48
48
|
import { buildWikilinkIndex, convertWikilinks } from "./wikilinks.mjs";
|
|
49
49
|
// One vocabulary of link findings, and one message per class, so the three
|
|
50
|
-
// resolvers cannot word the same defect differently
|
|
50
|
+
// resolvers cannot word the same defect differently.
|
|
51
51
|
import { linkFindingMessage } from "./wikilink-syntax.mjs";
|
|
52
|
-
// The declared tag vocabulary
|
|
52
|
+
// The declared tag vocabulary, which is where `draft` is stated. Read
|
|
53
53
|
// from there rather than respelt, so the tag and its one reader cannot drift.
|
|
54
54
|
import { isDraftNote } from "./note-vocabulary.mjs";
|
|
55
55
|
import { expandContentTables } from "./content-tables.mjs";
|
|
56
56
|
import { positionInBody } from "./diagnostics.mjs";
|
|
57
57
|
// The pure `sohl:` frontmatter readers live in a leaf module so the item-type
|
|
58
|
-
// registry can import them without reaching back through this one
|
|
58
|
+
// registry can import them without reaching back through this one.
|
|
59
59
|
// Re-exported here so every existing importer keeps its single import path.
|
|
60
60
|
import { getFrontmatter } from "./frontmatter.mjs";
|
|
61
61
|
export {
|
|
@@ -75,9 +75,21 @@ export {
|
|
|
75
75
|
* `html: true` is long-standing and load-bearing — notes carry raw blocks — and
|
|
76
76
|
* it is also why {@link module:engine/content-icons} exists rather than an
|
|
77
77
|
* instruction to write `<i class="fa-solid …">` by hand: that would render on
|
|
78
|
-
* the two HTML surfaces and be silently dropped by the third
|
|
78
|
+
* the two HTML surfaces and be silently dropped by the third.
|
|
79
79
|
*/
|
|
80
|
-
export const md = markdownit({ html: true }).use(
|
|
80
|
+
export const md = markdownit({ html: true }).use(
|
|
81
|
+
// Resolved per render, not at import: this constant is built before any
|
|
82
|
+
// configuration is read, and a package's own icons live in the
|
|
83
|
+
// configuration. A tree with none — or a caller with no configuration to
|
|
84
|
+
// find — falls back to the shipped table.
|
|
85
|
+
iconPlugin(() => {
|
|
86
|
+
try {
|
|
87
|
+
return loadPackConfig().icons;
|
|
88
|
+
} catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
81
93
|
|
|
82
94
|
/**
|
|
83
95
|
* Parses a markdown file with YAML frontmatter.
|
|
@@ -86,7 +98,7 @@ export const md = markdownit({ html: true }).use(iconPlugin());
|
|
|
86
98
|
* `body` is the trimmed raw markdown after the frontmatter block, and
|
|
87
99
|
* `description` is `body` rendered to HTML. `bodyLine` / `bodyColumn` are the
|
|
88
100
|
* 1-based **file** position of the body's first character, which is what turns
|
|
89
|
-
* an offset within `body` into a position a diagnostic can name
|
|
101
|
+
* an offset within `body` into a position a diagnostic can name — see
|
|
90
102
|
* {@link positionInBody}. If the file has no frontmatter block, returns
|
|
91
103
|
* `{ frontmatter: null, body: "", description: "" }` with a warn log, and no
|
|
92
104
|
* position: there is no body to have one.
|
|
@@ -108,7 +120,7 @@ export function parseMarkdownFile(filePath) {
|
|
|
108
120
|
const body = raw.trim();
|
|
109
121
|
const description = body ? md.render(body) : "";
|
|
110
122
|
// Where the trimmed body starts in the *file*, so an offset within it can
|
|
111
|
-
// be reported as a file position
|
|
123
|
+
// be reported as a file position. The frontmatter's lines and the
|
|
112
124
|
// blank lines `trim()` removes both sit in between, and the trim can take
|
|
113
125
|
// indentation off the first line as well — hence a column, not just a line.
|
|
114
126
|
const bodyStart = content.length - raw.length + (raw.length - raw.trimStart().length);
|
|
@@ -118,28 +130,10 @@ export function parseMarkdownFile(filePath) {
|
|
|
118
130
|
return { frontmatter, body, description, bodyLine, bodyColumn };
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
/**
|
|
122
|
-
* Recursively yields every `.md` file under `rootDir`, parsed.
|
|
123
|
-
* Yields `{ frontmatter, body, description, file, absPath, bodyLine,
|
|
124
|
-
* bodyColumn }` for each match — the last two from
|
|
125
|
-
* {@link parseMarkdownFile}, so a caller can report a position inside the
|
|
126
|
-
* body as a position in the file (#17).
|
|
127
|
-
* Silently skips directories that don't exist.
|
|
128
|
-
*
|
|
129
|
-
* Directory names in `skipDirectories` are ignored wherever they appear. The
|
|
130
|
-
* walk itself knows nothing about what they mean: `Templates/` is an Obsidian
|
|
131
|
-
* templater convention this repository's vault happens to use, not a property
|
|
132
|
-
* of a content tree, so it is configured rather than hard-coded (#1508).
|
|
133
|
-
*
|
|
134
|
-
* @param {string} rootDir - Root of the tree to walk.
|
|
135
|
-
* @param {object} [opts]
|
|
136
|
-
* @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore.
|
|
137
|
-
* Defaults to the configured list.
|
|
138
|
-
*/
|
|
139
133
|
/**
|
|
140
134
|
* Refuse a corpus read whose scope its caller did not state.
|
|
141
135
|
*
|
|
142
|
-
* The rule
|
|
136
|
+
* The rule in one place, so every reader of the tree refuses the same
|
|
143
137
|
* way and says so in the same words. It is shared rather than repeated because
|
|
144
138
|
* the corpus is no longer read only by {@link walkMarkdownTree}: a pass that
|
|
145
139
|
* reads the content index instead is making the identical claim about which
|
|
@@ -172,7 +166,7 @@ export function assertStatedScope(skipDirectories, who) {
|
|
|
172
166
|
* cycle. They take the records their caller already holds.
|
|
173
167
|
*
|
|
174
168
|
* That is not a workaround dressed up as a rule. A compile runs several passes
|
|
175
|
-
* over one tree, and the whole
|
|
169
|
+
* over one tree, and the whole point is that they must not each answer "which
|
|
176
170
|
* files are the corpus?" for themselves. Requiring the answer to be handed in
|
|
177
171
|
* makes the sharing structural rather than remembered.
|
|
178
172
|
*
|
|
@@ -191,8 +185,33 @@ export function assertSuppliedCorpus(records, who) {
|
|
|
191
185
|
}
|
|
192
186
|
}
|
|
193
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Recursively yields every `.md` file under `rootDir`, parsed.
|
|
190
|
+
*
|
|
191
|
+
* Yields `{ frontmatter, body, description, file, absPath, bodyLine,
|
|
192
|
+
* bodyColumn }` for each match — the last from {@link parseMarkdownFile}, so a
|
|
193
|
+
* caller can report a position inside the body as a position in the file. A
|
|
194
|
+
* root that does not exist yields nothing, and a directory that cannot be read
|
|
195
|
+
* is warned about and skipped.
|
|
196
|
+
*
|
|
197
|
+
* Directory names in `skipDirectories` are ignored wherever they appear. The
|
|
198
|
+
* walk itself knows nothing about what they mean: `Templates/` is an Obsidian
|
|
199
|
+
* templater convention this repository's vault happens to use, not a property
|
|
200
|
+
* of a content tree, so it is stated by the caller rather than hard-coded.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} rootDir - Root of the tree to walk.
|
|
203
|
+
* @param {object} opts
|
|
204
|
+
* @param {readonly string[]} opts.skipDirectories - Directory names to ignore.
|
|
205
|
+
* Required: the scope is the caller's to state, so two passes cannot
|
|
206
|
+
* disagree about which files are the corpus.
|
|
207
|
+
* @yields {{frontmatter: object|null, body: string, description: string,
|
|
208
|
+
* file: string, absPath: string, bodyLine?: number, bodyColumn?: number}}
|
|
209
|
+
* One entry per `.md` file found.
|
|
210
|
+
* @throws {Error} When `skipDirectories` is not stated — see
|
|
211
|
+
* {@link assertStatedScope}.
|
|
212
|
+
*/
|
|
194
213
|
export function* walkMarkdownTree(rootDir, { skipDirectories } = {}) {
|
|
195
|
-
// Stated by the caller, never resolved here.
|
|
214
|
+
// Stated by the caller, never resolved here. A default here
|
|
196
215
|
// — `loadPackConfig().skipDirectories` — read whichever configuration
|
|
197
216
|
// resolved from the working directory rather than the one the caller was
|
|
198
217
|
// working under. In an ordinary build those are the same object and nothing
|
|
@@ -200,9 +219,9 @@ export function* walkMarkdownTree(rootDir, { skipDirectories } = {}) {
|
|
|
200
219
|
// `PACKAGE_BUILD_CONFIG` names one, or when the command runs from a
|
|
201
220
|
// worktree. Six of this function's twelve callers were on that default, so
|
|
202
221
|
// "which files are the corpus?" had two answers depending on who asked
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
222
|
+
// — the same defect class as `entriesForNote` reading `docEntryTypes` from
|
|
223
|
+
// the ambient config rather than the passed one, which a fixture can pass
|
|
224
|
+
// on indefinitely.
|
|
206
225
|
assertStatedScope(skipDirectories, "walkMarkdownTree");
|
|
207
226
|
if (!fs.existsSync(rootDir)) return;
|
|
208
227
|
const stack = [rootDir];
|
|
@@ -234,7 +253,7 @@ export function* walkMarkdownTree(rootDir, { skipDirectories } = {}) {
|
|
|
234
253
|
/**
|
|
235
254
|
* Every position a note may state its template priority at, for one system
|
|
236
255
|
* block, in the order they answer — and whichever of them the note actually
|
|
237
|
-
* wrote
|
|
256
|
+
* wrote.
|
|
238
257
|
*
|
|
239
258
|
* Three places, in the order the migration runs. The specification calls this
|
|
240
259
|
* `data.templatePriority`; `sohl-thalorna` already writes it there on 941
|
|
@@ -290,7 +309,7 @@ function findTemplatePriority(fm, block) {
|
|
|
290
309
|
|
|
291
310
|
/**
|
|
292
311
|
* The template priority a note states, for a system that treats an unstated one
|
|
293
|
-
* as "not a template" rather than as an authoring error
|
|
312
|
+
* as "not a template" rather than as an authoring error.
|
|
294
313
|
*
|
|
295
314
|
* Reads exactly the positions {@link resolveTemplatePriority} reads, including
|
|
296
315
|
* the retiring `archetype` spelling, and refuses the same contradiction — so
|
|
@@ -324,7 +343,7 @@ export function statedTemplatePriority(fm, label, { block = "sohl" } = {}) {
|
|
|
324
343
|
|
|
325
344
|
/**
|
|
326
345
|
* Resolve the required `templatePriority` frontmatter for an Item/Actor entry
|
|
327
|
-
* (the archetype contract
|
|
346
|
+
* (the archetype contract). The property is a nullable number that
|
|
328
347
|
* authors must state explicitly:
|
|
329
348
|
* - a number → the document is a template of that priority.
|
|
330
349
|
* - `null` → the document is not a template.
|
|
@@ -333,7 +352,7 @@ export function statedTemplatePriority(fm, label, { block = "sohl" } = {}) {
|
|
|
333
352
|
*
|
|
334
353
|
* Reads the positions {@link findTemplatePriority} lists: `data.templatePriority`
|
|
335
354
|
* first — the specified home — then the system block and the top level, and
|
|
336
|
-
* finally the retiring `archetype` spelling in the same two places
|
|
355
|
+
* finally the retiring `archetype` spelling in the same two places.
|
|
337
356
|
*
|
|
338
357
|
* @param {object} fm Parsed frontmatter.
|
|
339
358
|
* @param {string} label Human-readable context for error messages.
|
|
@@ -371,8 +390,7 @@ export function resolveTemplatePriority(fm, label, { block = "sohl" } = {}) {
|
|
|
371
390
|
|
|
372
391
|
/**
|
|
373
392
|
* The value a document's `system.templatePriority` carries, from the required
|
|
374
|
-
* `templatePriority` frontmatter (
|
|
375
|
-
* #266 / sohl#1836).
|
|
393
|
+
* `templatePriority` frontmatter (`archetype` is the legacy spelling).
|
|
376
394
|
*
|
|
377
395
|
* A **schema field**, so the tri-state is written out in full rather than
|
|
378
396
|
* expressed by a key's presence: a number is a template at that priority, and
|
|
@@ -404,11 +422,6 @@ export function makeFilename(name, id) {
|
|
|
404
422
|
return `${unidecode(name)}_${id}`.replace(/[^0-9a-zA-Z]+/g, "_") + ".json";
|
|
405
423
|
}
|
|
406
424
|
|
|
407
|
-
/**
|
|
408
|
-
* Standardize a name into a slug: lowercase, apostrophes removed,
|
|
409
|
-
* non-alphanumerics collapsed to single hyphens.
|
|
410
|
-
*/
|
|
411
|
-
|
|
412
425
|
/**
|
|
413
426
|
* The path prefixes that name a package other than the one being compiled.
|
|
414
427
|
*
|
|
@@ -459,7 +472,7 @@ function addressesAnotherPackage(s) {
|
|
|
459
472
|
*
|
|
460
473
|
* Content frontmatter (`img` / `portrait`) authors a single path that has to
|
|
461
474
|
* work for Foundry, the knowledgebase, and the website. **Its first segment
|
|
462
|
-
* says which package owns the file
|
|
475
|
+
* says which package owns the file**, and there are exactly three
|
|
463
476
|
* answers:
|
|
464
477
|
*
|
|
465
478
|
* | Authored path starts with | Owner | Emitted |
|
|
@@ -471,7 +484,7 @@ function addressesAnotherPackage(s) {
|
|
|
471
484
|
* So `icons/relic.svg` compiles to `systems/sohl/assets/icons/relic.svg` here
|
|
472
485
|
* and to `modules/sohl-thalorna/assets/icons/relic.svg` in a module — the asset
|
|
473
486
|
* root is derived from the configuration, and is the one place `systems/sohl`
|
|
474
|
-
* is ever spelled
|
|
487
|
+
* is ever spelled. An authored
|
|
475
488
|
* `systems/sohl/assets/icons/noun/shield.svg` is left exactly as written,
|
|
476
489
|
* whichever package is compiling it.
|
|
477
490
|
*
|
|
@@ -482,13 +495,13 @@ function addressesAnotherPackage(s) {
|
|
|
482
495
|
* `assets/artwork/`, so an authored `artwork/deity.webp` would have shipped
|
|
483
496
|
* unprefixed: a 404 in Foundry, reported by nothing. That a package owns its
|
|
484
497
|
* own tree is the fact; the directory names inside it are that package's
|
|
485
|
-
* business
|
|
498
|
+
* business.
|
|
486
499
|
*
|
|
487
500
|
* **Off-install addresses pass through too**, which is the same rule rather
|
|
488
501
|
* than a fourth: a URL, a `data:` URI, or a `/`-rooted path names something no
|
|
489
502
|
* package owns. See {@link addressesAnotherPackage}.
|
|
490
503
|
*
|
|
491
|
-
* **`banner:` does not follow this rule, deliberately
|
|
504
|
+
* **`banner:` does not follow this rule, deliberately.** It is not an
|
|
492
505
|
* asset path inside a Foundry install at all: it reaches no compiled document,
|
|
493
506
|
* and its only consumer is the Hugo theme, which prefixes a relative value with
|
|
494
507
|
* `images/` and joins it onto `params.cdnBaseURL`. The two fields look alike
|
|
@@ -496,7 +509,7 @@ function addressesAnotherPackage(s) {
|
|
|
496
509
|
* the CDN serves — so they are documented apart rather than reconciled into one
|
|
497
510
|
* rule that would be true of neither.
|
|
498
511
|
*
|
|
499
|
-
* **Two empties, and they mean opposite things
|
|
512
|
+
* **Two empties, and they mean opposite things.** `null` — or an absent
|
|
500
513
|
* key, which reaches here as `undefined` — means _unset_: the note names no art
|
|
501
514
|
* and the caller's default applies. `""` means _blank on purpose_: the note
|
|
502
515
|
* names no art **and wants none**, so no default may replace it. Both come back
|
|
@@ -515,7 +528,7 @@ function addressesAnotherPackage(s) {
|
|
|
515
528
|
* is `""` (`sohl/item-fields.mjs`), resolved from the very same shared top-level
|
|
516
529
|
* key the site emitter reads as the page title — so `title: null` stringifies
|
|
517
530
|
* into the compiled document as the literal `"null"`. One key, two destinations
|
|
518
|
-
* that disagree about what empty means; see
|
|
531
|
+
* that disagree about what empty means; see.
|
|
519
532
|
*
|
|
520
533
|
* This is translation only: the default for an unset path is domain-specific
|
|
521
534
|
* (actors default differently from items, and gear differently again), so each
|
|
@@ -526,11 +539,19 @@ function addressesAnotherPackage(s) {
|
|
|
526
539
|
* `itemArt()`, which runs the path back through this function so a registry
|
|
527
540
|
* entry and a note's `img:` are spelled the same way (#7).
|
|
528
541
|
*
|
|
542
|
+
* **A package with no asset root cannot answer at all.** `assetRoot` is derived
|
|
543
|
+
* from the package kind, and a `documentation` package has none: Foundry serves
|
|
544
|
+
* no files for it. Only a compiling pass reaches here, and a documentation
|
|
545
|
+
* package runs none, so a path arriving with no root to put it under is a pass
|
|
546
|
+
* running where it should not — reported as that, rather than emitted as
|
|
547
|
+
* `null/icons/relic.svg` into a document nobody would check.
|
|
548
|
+
*
|
|
529
549
|
* @param {string | null | undefined} raw - content-relative path from frontmatter.
|
|
530
|
-
* @param {{assetRoot: string}} [config] - The resolved build configuration.
|
|
550
|
+
* @param {{assetRoot: string|null}} [config] - The resolved build configuration.
|
|
531
551
|
* Defaults to this repository's.
|
|
532
552
|
* @returns {string | null} the Foundry-relative path; `""` for a deliberate
|
|
533
553
|
* blank, and `null` when the note names no art at all.
|
|
554
|
+
* @throws {Error} When the configuration has no asset root.
|
|
534
555
|
*/
|
|
535
556
|
export function resolveImg(raw, config = loadPackConfig()) {
|
|
536
557
|
// Unset — the caller's default applies. An absent key arrives as
|
|
@@ -541,6 +562,14 @@ export function resolveImg(raw, config = loadPackConfig()) {
|
|
|
541
562
|
if (s === "") return "";
|
|
542
563
|
// Somebody else's to serve — emit it exactly as authored.
|
|
543
564
|
if (addressesAnotherPackage(s)) return s;
|
|
565
|
+
if (!config.assetRoot) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`package-build: \`${s}\` names a file this package serves, and a ` +
|
|
568
|
+
`\`documentation\` package has no asset root to serve it from — ` +
|
|
569
|
+
`Foundry installs no such package. Address the owning package ` +
|
|
570
|
+
`(\`systems/…\`, \`modules/…\`) or a URL.`,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
544
573
|
// Ours, so root it where Foundry serves this package's files from.
|
|
545
574
|
return `${config.assetRoot}/${s}`;
|
|
546
575
|
}
|
|
@@ -564,7 +593,7 @@ export function resolveName(fm, defaultValue = "Unnamed") {
|
|
|
564
593
|
* its migration shims on: a record stamped older than a shim is rewritten by it
|
|
565
594
|
* on load. Every pack once shipped `coreVersion: "14"`, which sorts *below*
|
|
566
595
|
* every v14 build and so left all shipped content permanently eligible for
|
|
567
|
-
* every v14 migration
|
|
596
|
+
* every v14 migration.
|
|
568
597
|
*
|
|
569
598
|
* **Read from the configuration, not from the shipped manifest.** It used to
|
|
570
599
|
* open `paths.packageManifest` and take `compatibility.minimum` out of it,
|
|
@@ -598,7 +627,7 @@ export function supportedCoreVersion(config = loadPackConfig()) {
|
|
|
598
627
|
/**
|
|
599
628
|
* Default `_stats` block for compiled compendium entries.
|
|
600
629
|
*
|
|
601
|
-
* Every stamped identity is configuration
|
|
630
|
+
* Every stamped identity is configuration: four compilers used to pass
|
|
602
631
|
* the same frozen `"0.6.0"` literal, and `systemId` / `lastModifiedBy` were
|
|
603
632
|
* written into this function. `coreVersion` alone is *not* configuration — it
|
|
604
633
|
* comes from {@link supportedCoreVersion}, the configured Foundry floor,
|
|
@@ -623,13 +652,12 @@ export function buildStats(systemVersion = undefined, config = loadPackConfig())
|
|
|
623
652
|
}
|
|
624
653
|
|
|
625
654
|
/**
|
|
626
|
-
* The `_stats` block for one pack, stamped with the system that pack is for
|
|
627
|
-
* (#48).
|
|
655
|
+
* The `_stats` block for one pack, stamped with the system that pack is for.
|
|
628
656
|
*
|
|
629
657
|
* **`systemId` travels with `systemVersion`.** They are one decision, so where
|
|
630
658
|
* one is omitted both are. Stamping a per-pack version against a package-wide
|
|
631
659
|
* id would emit `systemId: sohl, systemVersion: 1.6.3` on HM3 documents — a
|
|
632
|
-
* *plausible lie*, which is worse than
|
|
660
|
+
* *plausible lie*, which is worse than a missing value, because
|
|
633
661
|
* nothing about it looks wrong.
|
|
634
662
|
*
|
|
635
663
|
* Resolution, in order:
|
|
@@ -679,6 +707,13 @@ export function defaultStats() {
|
|
|
679
707
|
return cachedDefaultStats;
|
|
680
708
|
}
|
|
681
709
|
|
|
710
|
+
// The one slug rule, re-exported so callers keep a single import path.
|
|
711
|
+
/**
|
|
712
|
+
* Standardize a name into a slug: lowercase, apostrophes removed,
|
|
713
|
+
* non-alphanumerics collapsed to single hyphens.
|
|
714
|
+
*/
|
|
715
|
+
export { slugify } from "./content-slug.mjs";
|
|
716
|
+
|
|
682
717
|
/**
|
|
683
718
|
* Stable 16-char hex id derived from `${namespace}:${value}`.
|
|
684
719
|
*
|
|
@@ -686,9 +721,6 @@ export function defaultStats() {
|
|
|
686
721
|
* resolver this one imports can derive ids too — and re-exported here for the
|
|
687
722
|
* passes that have always reached it through `helpers`.
|
|
688
723
|
*/
|
|
689
|
-
// The one slug rule, re-exported so callers keep a single import path.
|
|
690
|
-
export { slugify } from "./content-slug.mjs";
|
|
691
|
-
|
|
692
724
|
export { makeId } from "./ids.mjs";
|
|
693
725
|
|
|
694
726
|
// The content-type → document-type map, which decides *which* pack list a
|
|
@@ -708,11 +740,11 @@ import { collectAnchors } from "./anchors.mjs";
|
|
|
708
740
|
*
|
|
709
741
|
* Each note's pack is resolved here, once, and stored on its index entry: a
|
|
710
742
|
* UUID carries a pack name, so a repository shipping several packs of one type
|
|
711
|
-
*
|
|
743
|
+
* would otherwise address every one of them as the first. A note whose
|
|
712
744
|
* declaration is unroutable is indexed against the conventional name and left
|
|
713
745
|
* for the compile pass to report — the index has no business failing a build,
|
|
714
746
|
* and the pass fails it with a far better message. The one exception is a
|
|
715
|
-
* **retired** content type
|
|
747
|
+
* **retired** content type: this walk is the first to see every
|
|
716
748
|
* note together with its path, and unlike an unroutable declaration there is
|
|
717
749
|
* no pass that would ever claim such a note and report it.
|
|
718
750
|
*
|
|
@@ -720,6 +752,15 @@ import { collectAnchors } from "./anchors.mjs";
|
|
|
720
752
|
* @param {object} [router] - The pack router. Supplied by the calling pass so
|
|
721
753
|
* the index and the compile agree about where each note landed; defaults to
|
|
722
754
|
* this repository's own.
|
|
755
|
+
* @param {object} [opts]
|
|
756
|
+
* @param {readonly string[]} [opts.skipDirectories] - Part of the options bag
|
|
757
|
+
* every corpus reader takes; the scope is already settled by `records`.
|
|
758
|
+
* @param {object} [opts.config] - The resolved build configuration; loaded when
|
|
759
|
+
* omitted.
|
|
760
|
+
* @param {readonly object[]} [opts.records] - The corpus, derived once per
|
|
761
|
+
* compile and handed in. Required: see {@link assertSuppliedCorpus}.
|
|
762
|
+
* @param {object[]} [opts.problems] - Part of the same options bag; the notes
|
|
763
|
+
* the index cannot record are collected where the corpus is derived.
|
|
723
764
|
* @returns {{byShortcode: Map, types: Set}} From `buildWikilinkIndex`.
|
|
724
765
|
*/
|
|
725
766
|
export function buildContentLinkIndex(
|
|
@@ -740,11 +781,11 @@ export function buildContentLinkIndex(
|
|
|
740
781
|
const fm = authoredFrontmatter(record);
|
|
741
782
|
const absPath = noteFile(contentBase, record);
|
|
742
783
|
// The id a note's document is filed under: its authored pin, or the
|
|
743
|
-
// one derived from its canonical address
|
|
744
|
-
// against the configuration this build resolved —
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
//
|
|
784
|
+
// one derived from its canonical address. Derived by the index
|
|
785
|
+
// against the configuration this build resolved — never here through
|
|
786
|
+
// `resolveNoteId(fm)` with no package, which falls back to the ambient
|
|
787
|
+
// `contentPackage()` and so to whichever configuration the working
|
|
788
|
+
// directory answers with.
|
|
748
789
|
// What is left after that is a file with **no address** — no type, or
|
|
749
790
|
// no shortcode — which is not an addressable note and has no document
|
|
750
791
|
// to link to.
|
|
@@ -752,30 +793,30 @@ export function buildContentLinkIndex(
|
|
|
752
793
|
// The first walk of every note in the tree, and the only one holding
|
|
753
794
|
// both the declared type and the file that declares it — so a note
|
|
754
795
|
// left on a retired type is reported here, by name, rather than
|
|
755
|
-
// several frames deeper with nothing to go on
|
|
796
|
+
// several frames deeper with nothing to go on.
|
|
756
797
|
assertTypeNotRetired(fm.type, absPath);
|
|
757
798
|
const base = String(record.file.name).replace(/_/g, " ");
|
|
758
799
|
docs.push({
|
|
759
800
|
type: fm.type,
|
|
760
801
|
id: fm.id,
|
|
761
802
|
// Where this note's own document lands, and where the JournalEntry
|
|
762
|
-
// its prose compiles into lands — two documents, two packs
|
|
803
|
+
// its prose compiles into lands — two documents, two packs.
|
|
763
804
|
pack: router.resolveOrNull(fm, packForType(fm.type).docType),
|
|
764
805
|
docPack: router.resolveOrNull(fm, "JournalEntry"),
|
|
765
806
|
shortcode: fm.shortcode ?? null,
|
|
766
807
|
name: fm.name?.full ?? base,
|
|
767
|
-
// Whether the note is tagged `draft
|
|
808
|
+
// Whether the note is tagged `draft`. Read from the tag
|
|
768
809
|
// vocabulary that declares it, and used for one thing: a link
|
|
769
810
|
// *into* this note renders marked. It takes no part in resolution,
|
|
770
811
|
// so the note is indexed, compiled and published as any other.
|
|
771
812
|
draft: isDraftNote(fm),
|
|
772
813
|
// The anchors this note declares, carried so the *builds* can check
|
|
773
|
-
// a `#section` link and not only the checker
|
|
814
|
+
// a `#section` link and not only the checker. A foreign
|
|
774
815
|
// anchor has always been checked, because a fetched index
|
|
775
816
|
// publishes the map; a local one was not, because the set was
|
|
776
817
|
// discarded here — the walk yields the body and nothing read it.
|
|
777
818
|
// Read from the record rather than from a second reading of the
|
|
778
|
-
// note's headings — the one-anchor-reader rule
|
|
819
|
+
// note's headings — the one-anchor-reader rule.
|
|
779
820
|
anchors: new Set((record.anchors ?? []).map((anchor) => anchor.slug)),
|
|
780
821
|
});
|
|
781
822
|
}
|
|
@@ -783,7 +824,7 @@ export function buildContentLinkIndex(
|
|
|
783
824
|
// its own content index and this build fetched the ones it depends on, so
|
|
784
825
|
// a contributor without every repository checked out resolves the same
|
|
785
826
|
// links CI does — from an artifact the producer shipped rather than a copy
|
|
786
|
-
// this repository committed
|
|
827
|
+
// this repository committed.
|
|
787
828
|
const { index: foreign, stale } = loadForeignIndexes(
|
|
788
829
|
resolved,
|
|
789
830
|
[resolved.contentPackage],
|
|
@@ -810,18 +851,28 @@ export function buildContentLinkIndex(
|
|
|
810
851
|
* target in the content tree. Every compiler funnels through this so the
|
|
811
852
|
* diagnostic text and the leave-it-alone fallback are identical everywhere.
|
|
812
853
|
*
|
|
813
|
-
* Each report names the **file, line and column** the link sits on
|
|
854
|
+
* Each report names the **file, line and column** the link sits on, so
|
|
814
855
|
* it can be opened and fixed — and so two identical links on one note are
|
|
815
856
|
* tellable apart. That needs `file` and the note's `bodyLine` / `bodyColumn`;
|
|
816
857
|
* without them the diagnostic still reports, one field shorter, rather than
|
|
817
858
|
* inventing a position.
|
|
818
859
|
*
|
|
819
860
|
* @param {string} body - The note's markdown body, tables already expanded.
|
|
820
|
-
* @param {object} ctx
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
861
|
+
* @param {object} ctx
|
|
862
|
+
* @param {string} ctx.type - The source note's content type.
|
|
863
|
+
* @param {string} ctx.id - The source note's document id.
|
|
864
|
+
* @param {string} ctx.pack - The pack the note's own document lands in, which
|
|
865
|
+
* addresses a `[[#slug]]` self-link: its target is the source note itself, so
|
|
866
|
+
* it has no index entry.
|
|
867
|
+
* @param {string} ctx.docPack - The pack the note's documentation journal lands
|
|
868
|
+
* in, addressing a self-link the same way.
|
|
869
|
+
* @param {object} ctx.index - The address index every link resolves through.
|
|
870
|
+
* @param {string} ctx.name - The note, for the message.
|
|
871
|
+
* @param {string} [ctx.file] - The note's file, so a report names it.
|
|
872
|
+
* @param {number} [ctx.bodyLine] - 1-based file line of the body's first line.
|
|
873
|
+
* @param {number} [ctx.bodyColumn] - 1-based file column of the same character.
|
|
874
|
+
* @param {Array<{line: number, generated: boolean}>} [ctx.lineMap] - Which
|
|
875
|
+
* authored line each body line came from, from {@link expandNoteTables}.
|
|
825
876
|
* @returns {{markdown: string, unresolved: Array<object>}}
|
|
826
877
|
* @throws {Error} On any link that does not resolve — an unlabelled one, a
|
|
827
878
|
* target that is not an address, or an address nothing publishes. The error
|
|
@@ -875,8 +926,8 @@ export function convertNoteWikilinks(
|
|
|
875
926
|
};
|
|
876
927
|
|
|
877
928
|
for (const u of result.unresolved) {
|
|
878
|
-
// Every class fails, and every class is worded by the shared table
|
|
879
|
-
//
|
|
929
|
+
// Every class fails, and every class is worded by the shared table.
|
|
930
|
+
// The three resolvers read one authored link, so an author who
|
|
880
931
|
// ran the pack build first and the link checker second must not be told
|
|
881
932
|
// two different things about the same mistake — and a class the pack
|
|
882
933
|
// build alone knew how to describe is how they came apart before.
|
|
@@ -898,6 +949,15 @@ export function convertNoteWikilinks(
|
|
|
898
949
|
* a table that leaves rows tied still emits identically on every build.
|
|
899
950
|
*
|
|
900
951
|
* @param {string} contentBase - Root of the content tree.
|
|
952
|
+
* @param {object} [opts]
|
|
953
|
+
* @param {readonly string[]} [opts.skipDirectories] - Part of the options bag
|
|
954
|
+
* every corpus reader takes; the scope is already settled by `records`.
|
|
955
|
+
* @param {object} [opts.config] - The resolved build configuration; loaded when
|
|
956
|
+
* omitted.
|
|
957
|
+
* @param {readonly object[]} [opts.records] - The corpus, derived once per
|
|
958
|
+
* compile and handed in. Required: see {@link assertSuppliedCorpus}.
|
|
959
|
+
* @param {object[]} [opts.problems] - Part of the same options bag; the notes
|
|
960
|
+
* the walk cannot read are collected where the corpus is derived.
|
|
901
961
|
* @returns {Array<{fm: object, path: string, tld: string, folder: string,
|
|
902
962
|
* absPath: string}>}
|
|
903
963
|
*/
|
|
@@ -916,9 +976,8 @@ export function collectContentDocs(
|
|
|
916
976
|
docs.push({
|
|
917
977
|
// With its package supplied for a `WHERE … package = "…"` query —
|
|
918
978
|
// synthesised from the configuration this build resolved, since no
|
|
919
|
-
// note declares it
|
|
920
|
-
// configuration in a worktree or under `PACKAGE_BUILD_CONFIG
|
|
921
|
-
// (#243).
|
|
979
|
+
// note declares it and the ambient one is a different
|
|
980
|
+
// configuration in a worktree or under `PACKAGE_BUILD_CONFIG`.
|
|
922
981
|
fm: searchableFrontmatter(fm, resolved.contentPackage),
|
|
923
982
|
// POSIX-separated and relative to the content root — what a
|
|
924
983
|
// `path:` search term globs, on every platform.
|
|
@@ -953,7 +1012,7 @@ const packLinkable = (doc) => Boolean(doc.fm?.shortcode) && Boolean(doc.fm?.type
|
|
|
953
1012
|
* A table searches the whole tree, which is one package's notes and nothing
|
|
954
1013
|
* else — so there is no longer a package to scope on. It used to filter, back
|
|
955
1014
|
* when a tree could hold several packages' notes and `package:` said which was
|
|
956
|
-
* which; that field is retired and the filter with it
|
|
1015
|
+
* which; that field is retired and the filter with it.
|
|
957
1016
|
*
|
|
958
1017
|
* @param {string} body - The note's markdown body.
|
|
959
1018
|
* @param {object} ctx
|
|
@@ -963,10 +1022,14 @@ const packLinkable = (doc) => Boolean(doc.fm?.shortcode) && Boolean(doc.fm?.type
|
|
|
963
1022
|
* query's `this` reads. Its entry in `docs` supplies the path as well.
|
|
964
1023
|
* @param {number} [ctx.bodyLine] - 1-based file line of the body's first line,
|
|
965
1024
|
* so a failing directive can be reported at its position in the file.
|
|
1025
|
+
* @param {object[]} [ctx.sqlTables] - This note's prepared `sql` results, in
|
|
1026
|
+
* document order, from
|
|
1027
|
+
* {@link module:engine/sql-tables.prepareSqlTables}. An `sql` directive with
|
|
1028
|
+
* no prepared result fails the note: nothing here runs a query.
|
|
966
1029
|
* @returns {{markdown: string, lineMap: Array<{line: number,
|
|
967
1030
|
* generated: boolean}>}} The body with every table expanded, and where each
|
|
968
1031
|
* emitted line came from — which is what lets a diagnostic about the
|
|
969
|
-
* expanded body name an authored position
|
|
1032
|
+
* expanded body name an authored position.
|
|
970
1033
|
* @throws {Error} When a query is malformed or unsupported — the note fails to
|
|
971
1034
|
* compile rather than shipping a table-shaped hole. The error carries
|
|
972
1035
|
* `position`, the directive's own line.
|