@heroiclands/package-build 23.7.0 → 23.8.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 +27 -0
- package/README.md +32 -0
- package/bin/content-commands.mjs +20 -6
- package/ci/check-banners.mjs +37 -19
- package/container.mjs +48 -10
- package/docs/api.md +87 -43
- package/docs/authoring/frontmatter.md +30 -1
- package/docs/authoring/links-and-markup.md +229 -36
- package/docs/commands.md +12 -2
- package/docs/content-format.md +24 -1
- package/docs/design.md +31 -0
- package/docs/guides/e2e.md +37 -1
- package/docs/project-setup.md +30 -15
- package/docs/reference/format-details.md +418 -220
- package/docs/reference/note-types.md +90 -31
- package/e2e.mjs +89 -2
- package/engine/base-compiler.mjs +11 -18
- package/engine/compile-corpus.mjs +2 -1
- package/engine/content-alerts.mjs +4 -2
- package/engine/content-blocks.mjs +18 -36
- package/engine/content-format.yaml +3 -0
- package/engine/content-index.mjs +35 -22
- package/engine/content-poetry.mjs +9 -0
- package/engine/diagnostics.mjs +24 -11
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/generate.mjs +12 -22
- package/engine/helpers.mjs +8 -6
- package/engine/infobox.mjs +3 -0
- package/engine/journals.mjs +11 -1
- package/engine/language-data.mjs +236 -1
- package/engine/language-rows.mjs +235 -3
- package/engine/language-terms.mjs +524 -9
- package/engine/language-words.mjs +75 -0
- package/engine/markdownlint-run.mjs +12 -2
- package/engine/markup-validation.mjs +34 -0
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-claims.mjs +19 -10
- package/engine/note-package.mjs +3 -5
- package/engine/note-vocabulary.mjs +29 -1
- package/engine/pdf-build.mjs +8 -1
- package/engine/prose-lint.mjs +12 -6
- package/engine/related-pages.mjs +22 -3
- package/engine/see-also.mjs +150 -0
- package/engine/site-build.mjs +5 -1
- package/engine/site-preview.mjs +4 -2
- package/engine/sql-tables.mjs +37 -12
- package/engine/standings.mjs +14 -12
- package/engine/word-rows.mjs +8 -3
- package/engine/word-terms.mjs +60 -2
- package/hugo-theme/layouts/partials/related.html +25 -8
- package/package.json +2 -1
- package/types/container.d.mts +4 -3
- package/types/e2e.d.mts +28 -0
- package/types/engine/base-compiler.d.mts +2 -2
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-index.d.mts +20 -7
- package/types/engine/diagnostics.d.mts +7 -6
- package/types/engine/helpers.d.mts +7 -4
- package/types/engine/language-rows.d.mts +89 -3
- package/types/engine/language-terms.d.mts +106 -4
- package/types/engine/markdownlint-run.d.mts +3 -1
- package/types/engine/markup-validation.d.mts +14 -0
- package/types/engine/note-addresses.d.mts +10 -0
- package/types/engine/note-claims.d.mts +7 -1
- package/types/engine/note-package.d.mts +3 -1
- package/types/engine/note-vocabulary.d.mts +1 -0
- package/types/engine/related-pages.d.mts +10 -1
- package/types/engine/see-also.d.mts +55 -0
- package/types/engine/sql-tables.d.mts +7 -3
- package/types/engine/word-rows.d.mts +2 -1
- package/types/engine/word-terms.d.mts +11 -0
package/docs/api.md
CHANGED
|
@@ -348,6 +348,20 @@ An addressed content note accepts exactly these top-level keys, in this order: `
|
|
|
348
348
|
| `dataFields` | `dataFields(type, vocabulary)` | `readonly DataFieldSpec[] \| undefined` | looking up the `data:` keys a note type may carry |
|
|
349
349
|
| `subTypes` | `subTypes(type, vocabulary)` | `readonly string[] \| null \| undefined` | looking up the closed `subType` values a note type declares |
|
|
350
350
|
|
|
351
|
+
### `./engine/see-also`
|
|
352
|
+
|
|
353
|
+
Import `@heroiclands/package-build/engine/see-also` for the deliberate
|
|
354
|
+
cross-references declared by `data.seeAlso`. Entries preserve authored order;
|
|
355
|
+
the caller supplies the medium's reference resolver and link renderer.
|
|
356
|
+
|
|
357
|
+
| Export | Signature | Returns | Use it when |
|
|
358
|
+
| ----------------- | ----------------------------------------------- | ----------------------------- | ------------------------------------------------------------------- |
|
|
359
|
+
| `checkSeeAlso` | `checkSeeAlso(note, { index, addressContext })` | `object[]` | locating malformed, unresolved, duplicate or self references |
|
|
360
|
+
| `seeAlsoEntries` | `seeAlsoEntries(fm, resolve)` | `object[]` | resolving names and destinations with optional glosses |
|
|
361
|
+
| `seeAlsoMarkdown` | `seeAlsoMarkdown(entries, link, level = 2)` | `string` | emitting an ordered See also section, empty when no entries resolve |
|
|
362
|
+
| `seeAlsoWebLink` | `seeAlsoWebLink(entry)` | `string` | writing a resolved book reference as a Markdown link |
|
|
363
|
+
| `seeAlsoPages` | `seeAlsoPages(pages, resolve)` | `Map<string, RelatedEntry[]>` | deriving ordered site connections for both builds and live previews |
|
|
364
|
+
|
|
351
365
|
### `engine.infobox`
|
|
352
366
|
|
|
353
367
|
The declared infobox: what a note's summary panel holds, decided once and rendered by each medium. The note box's fields are the type's own `data:` vocabulary, in its declared order, so a key added to a type appears everywhere with no second edit; the overlay declares only the label and the handful of keys that carry no row.
|
|
@@ -860,19 +874,19 @@ Emitting this package's cross-package link manifest. `engine/content-address.mjs
|
|
|
860
874
|
|
|
861
875
|
Emitting this package's content index. Every content build already walks the whole note tree and parses every note's frontmatter — the pack compilers, the site build, and the content-table expander each do it — and every one of them throws the result away when it finishes. So nothing outside a build can ask a question about the content: "which beings carry no `kbcat`?", "what does this table actually select?", "did that type rename leave anything behind?" have no answer short of writing a throwaway script that re-walks the tree. Eight dead Bestiary tables shipped for weeks behind exactly that gap.
|
|
862
876
|
|
|
863
|
-
| Export | Signature
|
|
864
|
-
| ----------------------- |
|
|
865
|
-
| `noteAddress` | `function noteAddress(frontmatter, contentPackage)`
|
|
866
|
-
| `sortKeysDeep` | `function sortKeysDeep(value)`
|
|
867
|
-
| `asciiName` | `function asciiName(name)`
|
|
868
|
-
| `asciiAliases` | `function asciiAliases(aliases)`
|
|
869
|
-
| `serializeContentIndex` | `function serializeContentIndex(records)`
|
|
870
|
-
| `indexRecordsFor` | `indexRecordsFor({ contentBase, config, skipDirectories, problems })` | {object[]} One record per note, plus one per documentation entry. | The index records for a content tree, without writing anything. |
|
|
871
|
-
| `indexRecordsForNote` | `indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext })`
|
|
872
|
-
| `emitContentIndex` | `emitContentIndex({ contentBase, outDir, config })`
|
|
873
|
-
| `buildIndexRecord` | `buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest })`
|
|
874
|
-
| `collectContentIndex` | `collectContentIndex(contentBase, { contentPackage, skipDirectories, assetsBase, manifest, problems, foreignRecord })` | `Array<Record<string, any>>` | reading a whole content tree into index records, in a total order independent of directory-read order; an item note yields two records (its own and its documentation journal's), and each asset root yields one per file; each note carries its resolved culture as `data.culture` |
|
|
875
|
-
| `resolveCultures` | `resolveCultures(records, { contentPackage, contentBase, foreignRecord, problems })`
|
|
877
|
+
| Export | Signature | Returns | Use it when |
|
|
878
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
879
|
+
| `noteAddress` | `function noteAddress(frontmatter, contentPackage)` | {{slug: string, canonical: string}\|null} `slug` is what goes inside `[[…]]` within this package; `canonical` is the fully qualified key the manifest files the note under, carrying the package and the system as well. | The address a wikilink writes to reach a note, or `null` when it has none. |
|
|
880
|
+
| `sortKeysDeep` | `function sortKeysDeep(value)` | {unknown} The value with every plain object's keys in sorted order. | Recursively sort an object's keys, so serialization is order-independent. |
|
|
881
|
+
| `asciiName` | `function asciiName(name)` | {string\|null} The ASCII form, or `null` when there is no name, or nothing printable survives. | A note's display name reduced to printable 7-bit ASCII. |
|
|
882
|
+
| `asciiAliases` | `function asciiAliases(aliases)` | {Array<string>} Possibly empty, never null: a note with no aliases has an empty set of them, which is a fact rather than a missing value, and a consumer iterating it should not have to check first. | A note's `name.aliases` reduced to printable 7-bit ASCII, in order. |
|
|
883
|
+
| `serializeContentIndex` | `function serializeContentIndex(records)` | {string} One compact JSON object per line, newline-terminated. | Serialize records as JSON Lines. |
|
|
884
|
+
| `indexRecordsFor` | `indexRecordsFor({ contentBase, config, skipDirectories, problems, parsed })` | {object[]} One record per note, plus one per documentation entry. | The index records for a content tree, without writing anything. |
|
|
885
|
+
| `indexRecordsForNote` | `indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, raw, contentPackage, manifest, addressContext })` | `object[]` | Derive the note's own record and its documentation record from parsed text. |
|
|
886
|
+
| `emitContentIndex` | `emitContentIndex({ contentBase, outDir, config })` | `{{file: string, notes: number, assets: number, records: number, bytes: number, full: number, draft: number, stub: number}}` Where it was written, how many notes and how many assets it holds, how many records that is in all, its size, and the ladder those notes sit on. | Emit this package's content index. |
|
|
887
|
+
| `buildIndexRecord` | `buildIndexRecord({ frontmatter, relPath, absPath, raw, contentPackage, body, bodyLine, manifest })` | `Record<string, any>` — the record, with derived fields sorted deep | building one note's content-index record; refuses a note that authors a key the index derives itself (`package` among them) before building anything |
|
|
888
|
+
| `collectContentIndex` | `collectContentIndex(contentBase, { contentPackage, skipDirectories, assetsBase, manifest, problems, foreignRecord, parsed })` | `Array<Record<string, any>>` | reading a whole content tree into index records, in a total order independent of directory-read order; an item note yields two records (its own and its documentation journal's), and each asset root yields one per file; each note carries its resolved culture as `data.culture` |
|
|
889
|
+
| `resolveCultures` | `resolveCultures(records, { contentPackage, contentBase, foreignRecord, problems })` | `Array<Record<string, any>>` — the same records | writing each note's resolved culture into its record as `data.culture`: its own value, else its parents', else for a polity its seat's and governed places', pooled; parents that disagree give a finding into `problems`, or one thrown Error listing every conflict |
|
|
876
890
|
|
|
877
891
|
`engine.contentIndex` also re-exports eight names from neighbouring leaf modules, at this same import path:
|
|
878
892
|
|
|
@@ -1197,7 +1211,7 @@ Shared helpers for the pack compilers in `packages/package-build/`. The HeroicLa
|
|
|
1197
1211
|
| ------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1198
1212
|
| `md` | `const md` | — | The markdown renderer every surface shares. |
|
|
1199
1213
|
| `renderFoundryMarkdown` | `function renderFoundryMarkdown(body)` | `string` — HTML with secret sections for GM passages. | Rendering a note body for a Foundry document. |
|
|
1200
|
-
| `parseMarkdownFile` | `function parseMarkdownFile(filePath)` | — | Parses a markdown file with YAML frontmatter.
|
|
1214
|
+
| `parseMarkdownFile` | `function parseMarkdownFile(filePath)` | — | Parses a markdown file with YAML frontmatter; returns frontmatter, body, rendered description, raw source, and body coordinates. |
|
|
1201
1215
|
| `assertStatedScope` | `function assertStatedScope(skipDirectories, who)` | {void} | Refuse a corpus read whose scope its caller did not state. |
|
|
1202
1216
|
| `assertSuppliedCorpus` | `function assertSuppliedCorpus(records, who)` | {void} | Refuse a corpus read whose records its caller did not supply. |
|
|
1203
1217
|
| `statedTemplatePriority` | `function statedTemplatePriority(fm, label,` | {number\|null} The priority, or `null` when the note is not a template or states nothing. | The template priority a note states, for a system that treats an unstated one as "not a template" rather than as an authoring error. |
|
|
@@ -1214,7 +1228,7 @@ Shared helpers for the pack compilers in `packages/package-build/`. The HeroicLa
|
|
|
1214
1228
|
| `convertNoteWikilinks` | `convertNoteWikilinks(body, { type, id, pack, docPack, index, name, file, bodyLine, bodyColumn, lineMap })` | `{{markdown: string, unresolved: Array<object>}}` | Converts the wikilinks in one note's markdown, reporting any that have no target in the content tree. |
|
|
1215
1229
|
| `expandNoteTables` | `function expandNoteTables(body,` | `{{markdown: string, lineMap: Array<{line: number, generated: boolean}>}}` The body with every table expanded, and where each emitted line came from — which is what lets a diagnostic about the expanded body name an authored position. | Expand prepared SQL tables in one note's markdown, before wikilinks are resolved — so a generated cell may itself be a wikilink. |
|
|
1216
1230
|
| `folderFilename` | `function folderFilename(name, id)` | — | Builds a compendium-source filename for a folder JSON document: `folder_Name_id.json` with non-alphanumeric runs replaced by underscores. |
|
|
1217
|
-
| `walkMarkdownTree` | `walkMarkdownTree(rootDir, { skipDirectories })` | generator yielding `{frontmatter, body, description, bodyLine?, bodyColumn?, file, absPath}`
|
|
1231
|
+
| `walkMarkdownTree` | `walkMarkdownTree(rootDir, { skipDirectories })` | generator yielding `{frontmatter, body, description, raw, bodyLine?, bodyColumn?, file, absPath}` | walking a content tree and reading every markdown file's frontmatter and body; `skipDirectories` is required rather than defaulted, so two callers cannot silently disagree about the corpus's scope |
|
|
1218
1232
|
|
|
1219
1233
|
### `engine.itemRegistry`
|
|
1220
1234
|
|
|
@@ -1807,6 +1821,12 @@ console.log(dataEnvVar("dev"));
|
|
|
1807
1821
|
| `removeContainer` | `removeContainer(name, log = () => {})` | — | stopping and removing a container, tolerating "not running" and "no such container" |
|
|
1808
1822
|
| `containerAction` | `containerAction({ action, stage, config, env, log })` | `number` | performing one `CONTAINER_ACTIONS` action for a stage end to end |
|
|
1809
1823
|
|
|
1824
|
+
`ensureS3` requires readable regular configuration files. Reuse requires an
|
|
1825
|
+
inspectable identities bind pointing to the current file; an incompatible bind
|
|
1826
|
+
throws with a scoped recovery command and leaves the server and bucket data
|
|
1827
|
+
intact. Both new and reused servers must answer their S3 HTTP health endpoint
|
|
1828
|
+
before the bucket is ensured.
|
|
1829
|
+
|
|
1810
1830
|
## `./coverage`
|
|
1811
1831
|
|
|
1812
1832
|
Whether the localization keys a package **references** and the keys it **declares** are the same set. A referenced-but-undeclared key is an error — it renders as its raw key string to a player; a declared-but-unreferenced key is advisory only, since no scan can see every way a key might be reached. Generic Foundry reference shapes (`{{localize}}`, `game.i18n.localize`, `LOCALIZATION_PREFIXES`, template literals) are read here directly; a repository that mints keys by its own convention contributes a named module through configuration. Everything exported is pure — source text in, references or findings out.
|
|
@@ -1854,7 +1874,7 @@ console.log(parseRemote("user@example.org:/srv/foundry"));
|
|
|
1854
1874
|
|
|
1855
1875
|
## `./e2e`
|
|
1856
1876
|
|
|
1857
|
-
The end-to-end harness: a disposable Foundry world, served from a container, with a browser suite driven against it. The
|
|
1877
|
+
The end-to-end harness: a disposable Foundry world, served from a container, with a browser suite driven against it. The suite is named in `packageBuild.e2e.suite`. For recognizable Cypress commands, the harness supplies pinned Chrome for Testing unless the arguments select a browser. It stands the licensed Foundry up, seeds a world with a known Gamemaster password, waits for that world to be _active_, and tears it down again. Three shapes of run answer different questions: `run` (from scratch, the only path that may change Foundry build), `fast` (the iteration loop — rebuild, redeploy, cycle, re-run), and `sweep` (the same full run against a build the repository does not pin, so `compatibility.verified` can be evidence rather than hope).
|
|
1858
1878
|
|
|
1859
1879
|
```js
|
|
1860
1880
|
import { hashPassword, E2E_MODES } from "@heroiclands/package-build/e2e";
|
|
@@ -1865,33 +1885,36 @@ console.log(E2E_MODES);
|
|
|
1865
1885
|
// -> ["run", "open"]
|
|
1866
1886
|
```
|
|
1867
1887
|
|
|
1868
|
-
| Export | Signature
|
|
1869
|
-
| ----------------------------- |
|
|
1870
|
-
| `E2E_GM_ID` | `const E2E_GM_ID`
|
|
1871
|
-
| `E2E_SCENE_ID` | `const E2E_SCENE_ID`
|
|
1872
|
-
| `E2E_MODES` | `const E2E_MODES`
|
|
1873
|
-
| `hashPassword` | `hashPassword(password, salt)`
|
|
1874
|
-
| `resolveE2EWorld` | `resolveE2EWorld(config, env = process.env)`
|
|
1875
|
-
| `worldManifest` | `worldManifest({ worldId, worldTitle, worldDescription, systemId, systemVersion, coreVersion })`
|
|
1876
|
-
| `gmDocument` | `gmDocument({ id, name, password, salt })`
|
|
1877
|
-
| `defaultSceneDocument` | `defaultSceneDocument()`
|
|
1878
|
-
| `moduleConfigurationDocument` | `moduleConfigurationDocument(packageId)`
|
|
1879
|
-
| `isWorldActive` | `isWorldActive(body)`
|
|
1880
|
-
| `resolveSweepVersion` | `resolveSweepVersion(argv)`
|
|
1881
|
-
| `parseFastArgs` | `parseFastArgs(argv, build)`
|
|
1882
|
-
| `seedTestWorld` | `async seedTestWorld({ config, packageJson, env = process.env, log = () => {}, runScript })`
|
|
1883
|
-
| `deployForContainer` | `async deployForContainer({ config, env = process.env, log = () => {}, runScript })`
|
|
1884
|
-
| `waitForWorld` | `async waitForWorld({ url, container, stage, timeoutMs = 180_000, log = () => {} })`
|
|
1885
|
-
| `
|
|
1886
|
-
| `
|
|
1887
|
-
| `
|
|
1888
|
-
| `
|
|
1889
|
-
| `
|
|
1890
|
-
| `
|
|
1891
|
-
| `
|
|
1892
|
-
| `
|
|
1893
|
-
| `
|
|
1894
|
-
| `
|
|
1888
|
+
| Export | Signature | Returns | Use it when |
|
|
1889
|
+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1890
|
+
| `E2E_GM_ID` | `const E2E_GM_ID` | — | reading the fixed Gamemaster document id every seeded world carries |
|
|
1891
|
+
| `E2E_SCENE_ID` | `const E2E_SCENE_ID` | — | reading the seeded, pre-activated default scene's id |
|
|
1892
|
+
| `E2E_MODES` | `const E2E_MODES` | — | the closed set of modes the suite may run under (`"run"` / `"open"`) |
|
|
1893
|
+
| `hashPassword` | `hashPassword(password, salt)` | `string` — the hex hash | hashing a password the way Foundry's own `core/auth.mjs` does, to seed a login that actually works |
|
|
1894
|
+
| `resolveE2EWorld` | `resolveE2EWorld(config, env = process.env)` | `E2EWorld` | resolving the seeded world's identity from configuration, with an environment override under the repository's own variable prefix |
|
|
1895
|
+
| `worldManifest` | `worldManifest({ worldId, worldTitle, worldDescription, systemId, systemVersion, coreVersion })` | `object` — the world manifest | building the `world.json` a seeded world carries |
|
|
1896
|
+
| `gmDocument` | `gmDocument({ id, name, password, salt })` | `object` — the user document | building the seeded world's single Gamemaster user document |
|
|
1897
|
+
| `defaultSceneDocument` | `defaultSceneDocument()` | `object` — the scene document | building the one pre-activated scene every seeded world carries |
|
|
1898
|
+
| `moduleConfigurationDocument` | `moduleConfigurationDocument(packageId)` | `object` — the setting document | building the world setting that switches a module package on (a module must be activated; a system does not) |
|
|
1899
|
+
| `isWorldActive` | `isWorldActive(body)` | `boolean` | checking whether a `/join` response body shows a world that is actually active, not merely reachable |
|
|
1900
|
+
| `resolveSweepVersion` | `resolveSweepVersion(argv)` | `string` — the exact build, trimmed | reading the exact Foundry build a sweep was asked to run against; there is deliberately no default |
|
|
1901
|
+
| `parseFastArgs` | `parseFastArgs(argv, build)` | `FastArgs` | parsing the fast loop's CLI arguments against the repository's declared build table, in declaration order |
|
|
1902
|
+
| `seedTestWorld` | `async seedTestWorld({ config, packageJson, env = process.env, log = () => {}, runScript })` | `Promise<{worldDir: string, world: E2EWorld}>` | seeding the disposable world into the end-to-end stage's data root, replacing the world directory through a throwaway container so the Foundry container's user owns it |
|
|
1903
|
+
| `deployForContainer` | `async deployForContainer({ config, env = process.env, log = () => {}, runScript })` | `Promise<void>` | deploying the staged package to the end-to-end stage, removing the old install and handing the new one to the Foundry container's user through a throwaway container |
|
|
1904
|
+
| `waitForWorld` | `async waitForWorld({ url, container, stage, timeoutMs = 180_000, log = () => {} })` | `Promise<void>` | polling until a seeded world is active, or failing with a diagnosis (a licence failure is detected from the container log, not a timeout) |
|
|
1905
|
+
| `CHROME_BUILD_ID` | `const CHROME_BUILD_ID` | `string` | reading the exact Chrome for Testing build used by default for Cypress suites |
|
|
1906
|
+
| `browserCacheDir` | `browserCacheDir(env = process.env)` | `string` | locating the persistent browser cache: `HL_BROWSER_CACHE`, a sibling of `FOUNDRYVTT_CACHE`, under `HL_E2E_ROOT`, or the user's cache directory |
|
|
1907
|
+
| `prepareSuiteBrowser` | `async prepareSuiteBrowser({ command, args = [], env = process.env, platform = process.platform, arch = process.arch, log = () => {} })` | `Promise<string[]>` | appending the pinned Chrome executable to Cypress arguments, installing it when absent; explicit browser arguments and other suites pass through unchanged |
|
|
1908
|
+
| `suiteExecutables` | `suiteExecutables(command)` | `string[]` | reading every executable a suite command needs to exist before it can run at all |
|
|
1909
|
+
| `findExecutable` | `findExecutable(name, { cwd, env = process.env } = {})` | `string\|null` | finding an executable the way the child process would (`node_modules/.bin` first, then `PATH`) |
|
|
1910
|
+
| `missingExecutables` | `missingExecutables({ command, cwd, env = process.env })` | `string[]` | checking which of a suite command's executables are not there — asked again mid-run, since an install can pull the runner out from under a run in progress |
|
|
1911
|
+
| `freshResults` | `freshResults({ paths, since, cwd })` | `string[]` | finding which declared result paths a suite run actually wrote to since it was spawned (existence alone is not evidence) |
|
|
1912
|
+
| `suiteVerdict` | `suiteVerdict({ status, vanished = [], declared = [], fresh = [] })` | `SuiteVerdict` | deciding what a finished suite run is worth given its exit status and what it left behind; can only ever downgrade a verdict, never upgrade one |
|
|
1913
|
+
| `runSuite` | `runSuite({ command, args = [], cwd, results = [], env = process.env, log = () => {} })` | `number` — the suite's exit status | running the repository's suite, bracketed by before/after executable checks and a fresh-results check rather than trusting the exit status alone |
|
|
1914
|
+
| `suiteCommand` | `suiteCommand(config, mode)` | `string[]` | reading the declared suite command for a mode (`"run"` or `"open"`), or failing clearly when none is declared |
|
|
1915
|
+
| `e2eRun` | `async e2eRun({ config, packageJson, mode = "run", suiteArgs = [], env = process.env, log = () => {} })` | `Promise<number>` | running a full, from-scratch end-to-end pass: deploy, reseed, recreate the container, wait, run the suite, tear down (or leave it serving in `"open"` mode) |
|
|
1916
|
+
| `e2eFast` | `async e2eFast({ config, argv = [], env = process.env, log = () => {} })` | `Promise<number>` | iterating quickly during development: rebuild what changed, redeploy, cycle the world, wait, re-run |
|
|
1917
|
+
| `e2eSweep` | `async e2eSweep({ config, packageJson, argv = [], env = process.env, log = () => {} })` | `Promise<number>` | running the full suite against a build the repository does not pin, to license moving `compatibility.verified` forward |
|
|
1895
1918
|
|
|
1896
1919
|
## `./lang`
|
|
1897
1920
|
|
|
@@ -2027,6 +2050,27 @@ The engine wildcard subpath also exposes the body parsers below. Their figure ex
|
|
|
2027
2050
|
|
|
2028
2051
|
`scanFigures` retains `line` (1-based caption line), `bodyStart`/`bodyEnd` (0-based, exclusive-end target range), and `close` (last consumed target line), plus caption attributes, kind, label and number. Unnumbered captions have an empty label and consume no number. `collectAnchors` discovers heading, caption, div, poem and span identifiers; `markupAnchorFindings(body)` reports collisions involving non-page identifiers; Foundry maps non-page anchors to the actual containing page UUID. An unnumbered `ref` requires `form="title"` or `form="full"`.
|
|
2029
2052
|
|
|
2053
|
+
`engine/markup-validation.mjs` provides shared syntax validation:
|
|
2054
|
+
|
|
2055
|
+
| Export | Purpose |
|
|
2056
|
+
| ---------------- | ----------------------------------------------------------------------------------- |
|
|
2057
|
+
| `markupFindings` | Validate complete Markdown source and return diagnostics for the supplied filename. |
|
|
2058
|
+
|
|
2059
|
+
`markupFindings(source, file)` masks YAML frontmatter without moving source positions and returns block and alert scanner errors as `{file, line, column, severity, message}` diagnostics. Content lint, Markdown checks and formatting preflight share this validation; malformed files are not rewritten by formatting or Markdown fixes.
|
|
2060
|
+
|
|
2030
2061
|
`engine/content-alerts.mjs` exposes `ALERT_TYPES`, `scanAlerts(source)` and `renderAlerts(source, target)` and `alertMarkdownPlugin(md)`. The scanner returns `{blocks, errors}`; alert records carry `type`, `id`, `classes`, `attributes`, `title`, `body` and inclusive zero-based `start`/`end` source lines. The renderer produces attributed `aside.alert` wrappers while leaving alert body Markdown in the document's scope. Alert validation and anchor discovery run in site, Foundry and book pipelines.
|
|
2031
2062
|
|
|
2032
2063
|
Alert `title` is the displayed heading override; absent, it defaults to the label in `ALERT_TYPES`. It does not change the alert type’s icon or color.
|
|
2064
|
+
|
|
2065
|
+
The `parsed` map accepted by content-index collection is filled by absolute
|
|
2066
|
+
note path with `{ frontmatter, body, raw, bodyLine?, bodyColumn? }`. Frontmatter
|
|
2067
|
+
is the authored value before index derivation. `buildCompileCorpus` returns
|
|
2068
|
+
that map as `parsed` for its document passes. `unclaimedNoteFindings` accepts it
|
|
2069
|
+
alongside `records`. `locateFrontmatterKey` and `assertNoDeclaredPackage` accept
|
|
2070
|
+
`raw` in their options to locate findings without opening the note again.
|
|
2071
|
+
|
|
2072
|
+
`prepareSqlTables` accepts sources with an optional `authoredSqlBlocks` count,
|
|
2073
|
+
set by `joinGeneratedSections`. Subsequent fences are generated event queries
|
|
2074
|
+
whose stub-inclusive results are identical; only authored fences receive a
|
|
2075
|
+
stub-inclusion comparison. Query answers and comparisons are cached for one
|
|
2076
|
+
preparation and kept separate.
|
|
@@ -31,7 +31,7 @@ The formatter keeps a collection on one line when the full line is under 100 cha
|
|
|
31
31
|
|
|
32
32
|
## Shared facts and game mechanics
|
|
33
33
|
|
|
34
|
-
The [note-type reference](../reference/note-types.md) lists every field accepted under `data`, including the shared `id`, `pack`, `packFolder`, `harnworld`, and `
|
|
34
|
+
The [note-type reference](../reference/note-types.md) lists every field accepted under `data`, including the shared `id`, `pack`, `packFolder`, `harnworld`, `banner`, and `seeAlso` fields. A type such as `being` also has its own fields, including `homes` and `affiliations`. `culture` is shared by `being`, `place`, `affiliation`, `lore` and `doc`: it names a `lore` note of `subType: culture`, is required on a `doc` of `subType: settingguide`, and is inherited down `data.parents` by places and affiliations. See [culture in the index](../reference/format-details.md#culture-in-the-index). A field absent from a type's vocabulary is a lint error; an unrelated field is not silently repurposed.
|
|
35
35
|
|
|
36
36
|
A being's `data.archetypes` names the roles it can fill in an adventure. Choose every specific role that fits, such as `entertainer` for a performer or `guildsperson` for someone whose professional training or connections matter. Use `[commoner]` when no specific archetype fits. `commoner` cannot share the list with another value. The [archetype reference](../reference/format-details.md#actors) defines the available roles.
|
|
37
37
|
|
|
@@ -41,6 +41,35 @@ A `sohl:`, `hm3:` or `dnd5e:` block holds values for that Foundry system. Its `s
|
|
|
41
41
|
|
|
42
42
|
One body describes the subject for both systems. A being's `{#appearance}` and `{#dossier}` sections feed SoHL Actor `system.appearance` and `system.dossier`, and HM3 Actor `system.description` and `system.biography`. An Item's `{#description}` page becomes a shared JournalEntryPage. Its resolved `@UUID[...]` pointer is stored in SoHL Item `system.docHtml` and HM3 Item `system.description`. An Item with no `{#description}` anchor uses its first page. Other H1 pages remain linkable.
|
|
43
43
|
|
|
44
|
+
## Suggested reading
|
|
45
|
+
|
|
46
|
+
Use `data.seeAlso` for deliberate cross-references. Each list entry has a required
|
|
47
|
+
`address` naming another note and an optional `gloss`: a short phrase explaining
|
|
48
|
+
why to follow it. The list keeps the order you write.
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
data:
|
|
52
|
+
seeAlso:
|
|
53
|
+
- address: lore-harborhistory
|
|
54
|
+
gloss: The founding of the port
|
|
55
|
+
- address: place-breakwater
|
|
56
|
+
- address: thalorna-note-lore-searoutes
|
|
57
|
+
gloss: Routes beyond the bay
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Here the harbor history comes first, the breakwater needs no explanation, and
|
|
61
|
+
the sea routes belong to the declared `thalorna` dependency. Write bare Addresses,
|
|
62
|
+
without wikilink brackets or anchors; include the note type, since this field
|
|
63
|
+
has no default type. Local references default to this package and the `note`
|
|
64
|
+
system. A reference must resolve in this package or a declared dependency.
|
|
65
|
+
An unresolved address, duplicate destination, or reference to the note itself
|
|
66
|
+
is an error at that entry's address.
|
|
67
|
+
|
|
68
|
+
The website places these links and glosses in a **See also** group ahead of
|
|
69
|
+
inferred connections in **Related**. A destination also found among backlinks
|
|
70
|
+
or mentions appears only in See also. Foundry journals and books append a
|
|
71
|
+
**See also** section. An omitted or empty list adds no section or group.
|
|
72
|
+
|
|
44
73
|
## Place governments
|
|
45
74
|
|
|
46
75
|
A place may state its governing affiliation in `data.government`. The field type is `Address`, with `affiliation` as the default target type when the type is omitted. Use either a local shortcode or a fully qualified affiliation Address from this package or a fetched dependency:
|