@heroiclands/package-build 23.1.0 → 23.2.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 +71 -0
- package/bin/content-commands.mjs +68 -149
- package/bin/package-build.mjs +56 -10
- package/ci/check-banners.mjs +42 -6
- package/ci/confirm-npm-version.mjs +135 -0
- package/ci/content-format-reference.mjs +14 -5
- package/config.mjs +1 -0
- package/container.mjs +185 -4
- package/content-config.mjs +333 -210
- package/deploy.mjs +116 -10
- package/docs/api.md +238 -246
- package/docs/authoring/assets.md +11 -5
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +87 -0
- package/docs/commands.md +138 -99
- package/docs/configuration.md +277 -125
- package/docs/content-format.md +2 -0
- package/docs/diagnostics.md +48 -13
- package/docs/guides/book.md +341 -21
- package/docs/guides/e2e.md +13 -2
- package/docs/guides/icons.md +14 -4
- package/docs/guides/module-project.md +18 -8
- package/docs/guides/release-and-deploy.md +54 -6
- package/docs/guides/site.md +5 -2
- package/docs/project-setup.md +30 -10
- package/docs/reference/format-details.md +373 -170
- package/docs/reference/note-types.md +128 -109
- package/docs/reference/theme-pages.md +31 -6
- package/e2e.mjs +140 -28
- package/engine/actor-compiler.mjs +22 -31
- package/engine/address-charset.mjs +4 -17
- package/engine/address-fields.mjs +1 -1
- package/engine/address-render.mjs +12 -1
- package/engine/address-values.mjs +6 -0
- package/engine/address.mjs +46 -33
- package/engine/art-fields.mjs +30 -5
- package/engine/art-slots.mjs +10 -1
- package/engine/asset-bindings.mjs +12 -1
- package/engine/base-compiler.mjs +34 -65
- package/engine/being-age.mjs +2 -7
- package/engine/being-measurements.mjs +12 -1
- package/engine/calendar-artifacts.mjs +6 -0
- package/engine/calendar-choice.mjs +12 -1
- package/engine/calendar-format.mjs +12 -1
- package/engine/calendar-human.mjs +12 -1
- package/engine/compile-corpus.mjs +3 -1
- package/engine/content-address.mjs +9 -12
- package/engine/content-alerts.mjs +13 -1
- package/engine/content-blocks.mjs +14 -3
- package/engine/content-embeds.mjs +11 -3
- package/engine/content-figures.mjs +9 -0
- package/engine/content-footnotes.mjs +12 -1
- package/engine/content-format-check.mjs +11 -262
- package/engine/content-format.yaml +5 -2
- package/engine/content-images.mjs +3 -0
- package/engine/content-index.mjs +443 -20
- package/engine/content-links.mjs +29 -35
- package/engine/content-lint.mjs +2 -3
- package/engine/content-poetry.mjs +12 -1
- package/engine/content-slug.mjs +2 -14
- package/engine/content-spans.mjs +13 -1
- package/engine/content-tables.mjs +12 -1
- package/engine/culture-choice.mjs +210 -13
- package/engine/data-keys.mjs +19 -16
- package/engine/date-conversion.mjs +12 -1
- package/engine/declaration-check.mjs +9 -0
- package/engine/derived-fields.mjs +6 -7
- package/engine/diagnostics.mjs +34 -4
- package/engine/docs-checks.mjs +9 -0
- package/engine/document-subtypes.mjs +47 -34
- package/engine/event-views.mjs +139 -14
- package/engine/exported-scene.mjs +92 -28
- package/engine/extension-attributes.mjs +12 -1
- package/engine/field-spec.mjs +11 -102
- package/engine/folder-notes.mjs +0 -64
- package/engine/foreign-catalog.mjs +2 -1
- package/engine/format-generated.mjs +9 -0
- package/engine/foundry-entries.mjs +10 -28
- package/engine/frontmatter-lint.mjs +83 -392
- package/engine/frontmatter.mjs +20 -70
- package/engine/generate.mjs +10 -15
- package/engine/heading-attributes.mjs +12 -1
- package/engine/helpers.mjs +41 -102
- package/engine/holdings.mjs +1 -1
- package/engine/homepage.mjs +3 -10
- package/engine/ids.mjs +1 -167
- package/engine/index-records.mjs +6 -4
- package/engine/index.mjs +0 -3
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +13 -14
- package/engine/item-compiler.mjs +4 -10
- package/engine/item-docs.mjs +3 -5
- package/engine/item-registry.mjs +2 -10
- package/engine/journals.mjs +1 -5
- package/engine/literature-notes.mjs +4 -12
- package/engine/macros.mjs +1 -2
- package/engine/map-graphviz.mjs +12 -1
- package/engine/markdown-expressions.mjs +12 -1
- package/engine/markdownlint-run.mjs +567 -0
- package/engine/metadata-index.mjs +42 -12
- package/engine/note-addresses.mjs +143 -35
- package/engine/note-claims.mjs +14 -28
- package/engine/note-dates.mjs +3 -3
- package/engine/note-frontmatter.mjs +6 -0
- package/engine/note-ids.mjs +8 -14
- package/engine/note-package.mjs +8 -25
- package/engine/note-state.mjs +1 -3
- package/engine/note-vocabulary.mjs +148 -84
- package/engine/number-words.mjs +12 -1
- package/engine/pack-config.mjs +15 -25
- package/engine/pack-router.mjs +10 -16
- package/engine/pathnames.mjs +8 -22
- package/engine/pdf-build.mjs +773 -121
- package/engine/pdf-coverage.mjs +188 -0
- package/engine/pdf-index.mjs +54 -7
- package/engine/pdf-pages.mjs +247 -0
- package/engine/pdf-render.mjs +114 -4
- package/engine/pdf-toc.mjs +29 -32
- package/engine/project-init.mjs +46 -6
- package/engine/prose-config.mjs +1 -1
- package/engine/prose-lint.mjs +52 -88
- package/engine/readability-lint.mjs +9 -0
- package/engine/reckoning-markers.mjs +12 -1
- package/engine/reference-targets.mjs +7 -0
- package/engine/runtime-only-fields.mjs +5 -8
- package/engine/scene-levels.mjs +2 -2
- package/engine/scenes.mjs +1 -1
- package/engine/shortcode-references.mjs +7 -0
- package/engine/site-build.mjs +30 -16
- package/engine/site-index.mjs +10 -16
- package/engine/site-preview.mjs +6 -0
- package/engine/site-root.mjs +8 -1
- package/engine/social-tie-terms.mjs +12 -1
- package/engine/social-ties.mjs +3 -0
- package/engine/sql-tables.mjs +56 -20
- package/engine/standing-terms.mjs +11 -1
- package/engine/standings.mjs +11 -27
- package/engine/system-block.mjs +39 -141
- package/engine/system-vocabulary.mjs +6 -0
- package/engine/walk-files.mjs +69 -0
- package/engine/web-wikilinks.mjs +23 -3
- package/engine/wikilink-syntax.mjs +32 -21
- package/engine/wikilinks.mjs +27 -16
- package/engine/yaml-lint.mjs +7 -4
- package/githooks/attribution.mjs +8 -0
- package/githooks/commit-message.mjs +9 -0
- package/hm3/actors.mjs +9 -48
- package/hm3/default-item-art.mjs +9 -9
- package/hm3/item-fields.mjs +2 -9
- package/hm3/template-priority.mjs +6 -7
- package/hugo-theme/data/banner-kinds.yaml +78 -0
- package/hugo-theme/data/banners.yaml +2 -3
- package/hugo-theme/layouts/404.html +3 -3
- package/hugo-theme/layouts/partials/banner-url.html +31 -7
- package/hugo-theme/layouts/partials/hero-banner.html +13 -14
- package/hugo-theme/layouts/partials/related.html +1 -1
- package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
- package/hugo-theme/static/css/style.css +1 -1
- package/manifest.mjs +11 -4
- package/markdownlint-config.mjs +1 -1
- package/package.json +6 -3
- package/release.mjs +83 -18
- package/sohl/actors.mjs +16 -27
- package/sohl/default-item-art.mjs +3 -3
- package/sohl/document-subtypes.mjs +1 -16
- package/sohl/infobox.mjs +2 -3
- package/sohl/item-builders.mjs +5 -8
- package/sohl/item-fields.mjs +5 -35
- package/sohl/items.mjs +0 -4
- package/sohl/kb-passes.mjs +3 -3
- package/types/container.d.mts +116 -5
- package/types/content-config.d.mts +83 -35
- package/types/deploy.d.mts +42 -6
- package/types/e2e.d.mts +35 -3
- package/types/engine/address-charset.d.mts +4 -17
- package/types/engine/address.d.mts +17 -8
- package/types/engine/art-fields.d.mts +7 -4
- package/types/engine/art-slots.d.mts +6 -0
- package/types/engine/base-compiler.d.mts +15 -17
- package/types/engine/content-address.d.mts +9 -12
- package/types/engine/content-embeds.d.mts +4 -2
- package/types/engine/content-format-check.d.mts +0 -64
- package/types/engine/content-index.d.mts +82 -3
- package/types/engine/content-links.d.mts +2 -4
- package/types/engine/culture-choice.d.mts +51 -8
- package/types/engine/data-keys.d.mts +2 -2
- package/types/engine/derived-fields.d.mts +1 -2
- package/types/engine/diagnostics.d.mts +27 -4
- package/types/engine/document-subtypes.d.mts +12 -31
- package/types/engine/event-views.d.mts +14 -2
- package/types/engine/exported-scene.d.mts +3 -2
- package/types/engine/field-spec.d.mts +13 -122
- package/types/engine/folder-notes.d.mts +0 -39
- package/types/engine/foreign-catalog.d.mts +2 -1
- package/types/engine/foundry-entries.d.mts +4 -5
- package/types/engine/frontmatter-lint.d.mts +2 -2
- package/types/engine/frontmatter.d.mts +19 -45
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +25 -42
- package/types/engine/homepage.d.mts +3 -10
- package/types/engine/ids.d.mts +0 -114
- package/types/engine/index-records.d.mts +6 -4
- package/types/engine/index.d.mts +0 -1
- package/types/engine/infobox-render.d.mts +7 -1
- package/types/engine/infobox.d.mts +3 -2
- package/types/engine/item-docs.d.mts +1 -1
- package/types/engine/item-registry.d.mts +2 -4
- package/types/engine/literature-notes.d.mts +1 -1
- package/types/engine/markdownlint-run.d.mts +40 -0
- package/types/engine/metadata-index.d.mts +9 -1
- package/types/engine/note-addresses.d.mts +27 -2
- package/types/engine/note-claims.d.mts +3 -5
- package/types/engine/note-ids.d.mts +2 -4
- package/types/engine/note-package.d.mts +1 -1
- package/types/engine/note-state.d.mts +1 -3
- package/types/engine/note-vocabulary.d.mts +28 -21
- package/types/engine/pack-config.d.mts +2 -6
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/pathnames.d.mts +3 -3
- package/types/engine/pdf-build.d.mts +98 -12
- package/types/engine/pdf-coverage.d.mts +60 -0
- package/types/engine/pdf-index.d.mts +34 -5
- package/types/engine/pdf-pages.d.mts +143 -0
- package/types/engine/pdf-render.d.mts +45 -1
- package/types/engine/pdf-toc.d.mts +12 -14
- package/types/engine/prose-config.d.mts +1 -1
- package/types/engine/prose-lint.d.mts +11 -10
- package/types/engine/runtime-only-fields.d.mts +2 -2
- package/types/engine/site-build.d.mts +9 -5
- package/types/engine/site-index.d.mts +4 -5
- package/types/engine/site-root.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -2
- package/types/engine/standing-terms.d.mts +2 -1
- package/types/engine/standings.d.mts +7 -9
- package/types/engine/system-block.d.mts +10 -44
- package/types/engine/walk-files.d.mts +15 -0
- package/types/engine/wikilink-syntax.d.mts +18 -19
- package/types/engine/yaml-lint.d.mts +5 -4
- package/types/hm3/actors.d.mts +1 -2
- package/types/hm3/item-fields.d.mts +2 -4
- package/types/hm3/template-priority.d.mts +6 -7
- package/types/manifest.d.mts +3 -2
- package/types/release.d.mts +12 -7
- package/types/sohl/actors.d.mts +2 -1
- package/types/sohl/document-subtypes.d.mts +1 -2
- package/types/sohl/item-fields.d.mts +3 -7
- package/engine/retired-fields.mjs +0 -631
- package/types/engine/retired-fields.d.mts +0 -354
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
2
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
12
|
*/
|
|
4
13
|
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
2
13
|
|
|
3
14
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
4
15
|
import {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
3
|
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
4
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
12
|
*/
|
|
6
13
|
|
|
@@ -15,12 +15,9 @@
|
|
|
15
15
|
* Schema fields a note may **never** author, because the document writes them
|
|
16
16
|
* in play.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* refuses one it never could. The two read alike deliberately — a note says one
|
|
20
|
-
* thing, the build does another, and nothing says so — but they are different
|
|
21
|
-
* facts, and only one of them is ever swept: a retired field goes away, while
|
|
18
|
+
* A note says one thing and the build does another, and nothing says so:
|
|
22
19
|
* `onsetDate` is a permanent part of `affliction`'s schema that simply is not
|
|
23
|
-
* content.
|
|
20
|
+
* content, so it is refused by name.
|
|
24
21
|
*
|
|
25
22
|
* ## What was going wrong
|
|
26
23
|
*
|
|
@@ -65,7 +62,7 @@
|
|
|
65
62
|
|
|
66
63
|
import { runtimeOnlyFields } from "./field-spec.mjs";
|
|
67
64
|
import { getFrontmatter } from "./frontmatter.mjs";
|
|
68
|
-
import { locateFrontmatterKey } from "./
|
|
65
|
+
import { locateFrontmatterKey } from "./diagnostics.mjs";
|
|
69
66
|
import { SYSTEM_DATA_KEY, systemData } from "./system-block.mjs";
|
|
70
67
|
|
|
71
68
|
/**
|
|
@@ -107,7 +104,7 @@ export function runtimeOnlyMessage(key, field, file) {
|
|
|
107
104
|
/**
|
|
108
105
|
* The runtime-only fields a note actually writes, in declaration order.
|
|
109
106
|
*
|
|
110
|
-
* **Presence is the whole test
|
|
107
|
+
* **Presence is the whole test**: an authored
|
|
111
108
|
* `onsetDate: null` is as much a claim about play state as a number is, and it
|
|
112
109
|
* is exactly the belief the message exists to correct. So the question is
|
|
113
110
|
* whether the path resolves to anything at all, never whether the value is a
|
|
@@ -115,7 +112,7 @@ export function runtimeOnlyMessage(key, field, file) {
|
|
|
115
112
|
*
|
|
116
113
|
* Only `<block>.system.<to>` is searched, because it is the only position a
|
|
117
114
|
* runtime-only field is reachable at. Such a declaration carries no `name`, so
|
|
118
|
-
* it has neither
|
|
115
|
+
* it has neither an in-block key nor a shared top-level source — and a
|
|
119
116
|
* bare `<block>.onsetDate` is an unrecognized block key, which
|
|
120
117
|
* {@link module:engine/system-block.unknownBlockKeys} already reports as an
|
|
121
118
|
* error naming the note and the line.
|
package/engine/scene-levels.mjs
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* and yields an empty collection. The very next world launch migrates that
|
|
28
28
|
* scene, **persists `levels: []`**, and leaves `initialLevel` pointing at an id
|
|
29
|
-
* that
|
|
29
|
+
* that does not exist — measured on both 14.359 and 14.367. The map image is
|
|
30
30
|
* gone for good, and the only symptom a human sees is a blank battlemap.
|
|
31
31
|
*
|
|
32
32
|
* Foundry is behaving correctly there: a scene with no Level records genuinely
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* permanent. So the pack build asserts the invariant on the artefact it just
|
|
35
35
|
* wrote — the compiled LevelDB, not the JSON it was compiled from — because the
|
|
36
36
|
* gap this closes is the write path (the emitter is already unit-tested, and
|
|
37
|
-
* the compendium CLI
|
|
37
|
+
* the compendium CLI can mishandle Scene Levels).
|
|
38
38
|
*
|
|
39
39
|
* An {@link https://foundryvtt.com/api/classes/foundry.documents.BaseAdventure.html Adventure}
|
|
40
40
|
* carries its scenes *inline*, levels and all, so the same invariant has a
|
package/engine/scenes.mjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* A map note carries its Scene as exported from Foundry, at `data.scene`. The
|
|
19
19
|
* build never constructs a Scene: {@link module:engine/exported-scene} passes
|
|
20
|
-
* the export through, applying
|
|
20
|
+
* the export through, applying its fixups and binding pins marked `#anchor`
|
|
21
21
|
* to the note's own journal pages.
|
|
22
22
|
*
|
|
23
23
|
* **Two outputs, for two different jobs.**
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
3
|
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
4
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
12
|
*/
|
|
6
13
|
|
package/engine/site-build.mjs
CHANGED
|
@@ -71,6 +71,7 @@ import { renderFigureBlocks, scanFigures } from "./content-figures.mjs";
|
|
|
71
71
|
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
72
72
|
import { collectAnchors, eventAnchors, markupAnchorFindings } from "./anchors.mjs";
|
|
73
73
|
import { checkImages, renderImageFigures } from "./content-images.mjs";
|
|
74
|
+
import { isAddressTuple } from "./address.mjs";
|
|
74
75
|
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
75
76
|
import {
|
|
76
77
|
buildSiteIndex,
|
|
@@ -229,7 +230,7 @@ export function collectContentPages(contentBase, ctx) {
|
|
|
229
230
|
if (!note) continue;
|
|
230
231
|
applyComputedBeingAge(note.fm, present, dates);
|
|
231
232
|
const { fm, body, bodyLine } = note;
|
|
232
|
-
// The configuration's, never a note's: `package
|
|
233
|
+
// The configuration's, never a note's: a note declares no `package:`, so every
|
|
233
234
|
// note in the tree belongs to the package this repository compiles.
|
|
234
235
|
const pkg = ctx.contentPackage;
|
|
235
236
|
if (!ctx.packages.has(pkg) || !fm.type) continue;
|
|
@@ -377,15 +378,26 @@ export function collectHomepages(contentBase, ctx) {
|
|
|
377
378
|
* @param {import("./related-pages.mjs").Related} [options.related] - The
|
|
378
379
|
* homepage's backlinks and mentions. Absent where nothing connects to it,
|
|
379
380
|
* and when the tree contains only a homepage, where no content link resolves.
|
|
381
|
+
* @param {readonly object[]} [options.records] - The corpus, so a `data.banner`
|
|
382
|
+
* address resolves against the images this package ships.
|
|
383
|
+
* @param {object} [options.foreign] - The dependencies' vendored indexes, so a
|
|
384
|
+
* `data.banner` may name an image a dependency ships.
|
|
380
385
|
* @returns {number} How many pages were written.
|
|
381
386
|
*/
|
|
382
|
-
export function writeHomepages(outRoot, pages, config, { related } = {}) {
|
|
387
|
+
export function writeHomepages(outRoot, pages, config, { related, records, foreign } = {}) {
|
|
388
|
+
const artIndex = assetAddressIndex(records ?? [], { config, foreign });
|
|
389
|
+
// A pathname no asset host serves resolves to `null`, which the theme reads
|
|
390
|
+
// as an unset banner rather than as a URL to nowhere.
|
|
391
|
+
const webSrc = (src) => resolvePathname(src, config)?.web ?? null;
|
|
392
|
+
const artSrc = (value, type, accepts) => artPathname(artIndex, value, type, accepts).pathname;
|
|
383
393
|
for (const page of pages) {
|
|
384
394
|
const data = homepageFrontmatter(page.fm, {
|
|
385
395
|
contentPackage: config.contentPackage,
|
|
386
396
|
title: homepageTitle(page.fm, config),
|
|
387
397
|
related,
|
|
388
398
|
});
|
|
399
|
+
if (isPlainObject(data.data)) data.data = { ...data.data };
|
|
400
|
+
resolveArtFields(data, webSrc, artSrc);
|
|
389
401
|
const dest = path.join(outRoot, HOMEPAGE_DESTINATION);
|
|
390
402
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
391
403
|
fs.writeFileSync(dest, matter.stringify(page.body, encodeAddresses(data)));
|
|
@@ -532,9 +544,7 @@ export function tableUniverse(pages) {
|
|
|
532
544
|
/**
|
|
533
545
|
* The frontmatter a page publishes with.
|
|
534
546
|
*
|
|
535
|
-
* An authored `aliases` is
|
|
536
|
-
* here, which makes this a guard rather than a working path. It is a list of
|
|
537
|
-
* names a reader might call the note. Hugo reads
|
|
547
|
+
* An authored `aliases` is a list of names a reader might call the note. Hugo reads
|
|
538
548
|
* `aliases` as **URL redirects**, so passing them through would publish a
|
|
539
549
|
* redirect stub at each name. They are dropped, and this build emits no
|
|
540
550
|
* redirects of its own.
|
|
@@ -556,7 +566,7 @@ export function tableUniverse(pages) {
|
|
|
556
566
|
* `<base><slug>/`.
|
|
557
567
|
*
|
|
558
568
|
* A content page carries the package the build **derived**. No note
|
|
559
|
-
* declares one
|
|
569
|
+
* declares one, so the note's frontmatter alone
|
|
560
570
|
* would publish a page that does not say which package it belongs to. The
|
|
561
571
|
* emitted page is what a theme reads: `breadcrumbs.html` reads
|
|
562
572
|
* `.Params.package`. Writing the derived value keeps a page
|
|
@@ -629,7 +639,8 @@ function pageTitle(page) {
|
|
|
629
639
|
* added to the vocabulary reaches the page without anyone remembering this
|
|
630
640
|
* function exists. Only an authored **string** is touched: `null` is a note
|
|
631
641
|
* naming no art and `""` is one naming none on purpose, and neither is an
|
|
632
|
-
* address to resolve.
|
|
642
|
+
* address to resolve. A slot's declining word (`banner: none`) is emitted as
|
|
643
|
+
* a blank, the value a theme reads as "draw no image".
|
|
633
644
|
*
|
|
634
645
|
* **An address nothing answers is dropped**, not emitted as written. The theme
|
|
635
646
|
* renders nothing where a value is absent — its silent-disappear convention —
|
|
@@ -643,11 +654,15 @@ function pageTitle(page) {
|
|
|
643
654
|
* @returns {void}
|
|
644
655
|
*/
|
|
645
656
|
function resolveArtFields(data, webSrc, artSrc) {
|
|
646
|
-
for (const { key, type, accepts } of ART_SLOTS) {
|
|
657
|
+
for (const { key, type, accepts, declines } of ART_SLOTS) {
|
|
647
658
|
const holder = isPlainObject(data.data) ? data.data : null;
|
|
648
659
|
if (!holder) continue;
|
|
649
660
|
const value = holder[key];
|
|
650
|
-
if (
|
|
661
|
+
if (declines !== undefined && value === declines) {
|
|
662
|
+
holder[key] = "";
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if ((typeof value !== "string" && !isAddressTuple(value)) || value === "") continue;
|
|
651
666
|
const pathname = artSrc(value, type, accepts);
|
|
652
667
|
if (pathname === null) delete holder[key];
|
|
653
668
|
else holder[key] = webSrc(pathname);
|
|
@@ -1229,8 +1244,8 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1229
1244
|
|
|
1230
1245
|
const ctx = {
|
|
1231
1246
|
packages,
|
|
1232
|
-
// The package every note in the tree belongs to.
|
|
1233
|
-
//
|
|
1247
|
+
// The package every note in the tree belongs to. a note declares no
|
|
1248
|
+
// `package:`, so this is the only source of it.
|
|
1234
1249
|
contentPackage: resolved.contentPackage,
|
|
1235
1250
|
skipDirectories: resolved.skipDirectories,
|
|
1236
1251
|
config: resolved,
|
|
@@ -1292,7 +1307,7 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1292
1307
|
}
|
|
1293
1308
|
|
|
1294
1309
|
// The whole tree is a build artifact, regenerated every run: a page whose
|
|
1295
|
-
// note was deleted or
|
|
1310
|
+
// note was deleted or moved would otherwise linger and keep publishing.
|
|
1296
1311
|
fs.rmSync(outBase, { recursive: true, force: true });
|
|
1297
1312
|
|
|
1298
1313
|
// Homepage-only stops here, and stopping is the point: nothing below reads
|
|
@@ -1313,7 +1328,7 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1313
1328
|
embedErrors: [],
|
|
1314
1329
|
mapFindings: [],
|
|
1315
1330
|
stats: {
|
|
1316
|
-
homepages: writeHomepages(homeRoot, homepages, resolved),
|
|
1331
|
+
homepages: writeHomepages(homeRoot, homepages, resolved, { records: ctx.records }),
|
|
1317
1332
|
out: homeRoot,
|
|
1318
1333
|
},
|
|
1319
1334
|
};
|
|
@@ -1397,9 +1412,6 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1397
1412
|
maps: drawn.maps,
|
|
1398
1413
|
pass,
|
|
1399
1414
|
// What counts as a being is the toolchain's to say, not a consumer's.
|
|
1400
|
-
// Asking in a consumer's script is how one came to still be checking
|
|
1401
|
-
// `character` and `creature` months after they were retired, and to
|
|
1402
|
-
// publish 95 pages with empty sidebars for months without noticing.
|
|
1403
1415
|
decorate: sitePageDecorator(resolved, gates.index),
|
|
1404
1416
|
});
|
|
1405
1417
|
|
|
@@ -1408,6 +1420,8 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1408
1420
|
// graph keys its block.
|
|
1409
1421
|
const homepagesWritten = writeHomepages(homeRoot, homepages, resolved, {
|
|
1410
1422
|
related: rendered.related.get(base),
|
|
1423
|
+
records: ctx.records,
|
|
1424
|
+
foreign: gates.foreign,
|
|
1411
1425
|
});
|
|
1412
1426
|
|
|
1413
1427
|
return {
|
package/engine/site-index.mjs
CHANGED
|
@@ -36,11 +36,8 @@
|
|
|
36
36
|
* matched by the segments it supplies rather than fetched by an exact key —
|
|
37
37
|
* one hit resolves, and anything else is a finding.
|
|
38
38
|
*
|
|
39
|
-
* **A page's *name* is not a key
|
|
40
|
-
*
|
|
41
|
-
* made two pages of one type forbidden from sharing a display name. The
|
|
42
|
-
* bare form is retired, so the fallbacks answer nothing and the constraint they
|
|
43
|
-
* imposed is gone with them.
|
|
39
|
+
* **A page's *name* is not a key**, so two pages of one type may share a
|
|
40
|
+
* display name.
|
|
44
41
|
*
|
|
45
42
|
* **It reports rather than exits.** A build script owns its diagnostics and its
|
|
46
43
|
* exit code; this returns what it found. That is the same rule the rest of the
|
|
@@ -200,11 +197,9 @@ export function buildSiteIndex(
|
|
|
200
197
|
const packages = new Set(ownPackage ? [ownPackage] : []);
|
|
201
198
|
|
|
202
199
|
// A page is addressed by `(type, shortcode)` and nothing else. Its name,
|
|
203
|
-
// filename and bare slug are not keys
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
// section is a listing the configuration declares, not part of any
|
|
207
|
-
// address.
|
|
200
|
+
// filename and bare slug are not keys, so two pages of a type may share
|
|
201
|
+
// a name. A page carries no section either — a section is a listing the
|
|
202
|
+
// configuration declares, not part of any address.
|
|
208
203
|
|
|
209
204
|
// A foreign package may use a type this build has never seen. Seeding those
|
|
210
205
|
// is what lets the resolver recognise `polity-xyz` as an address at all —
|
|
@@ -265,7 +260,7 @@ export function buildSiteIndex(
|
|
|
265
260
|
// canonical form is what cross-package links use.
|
|
266
261
|
// The page's package is the configured one — the site collection
|
|
267
262
|
// resolves it and records it as `pkg`. Never read out of
|
|
268
|
-
// frontmatter: `package
|
|
263
|
+
// frontmatter: a note declares no `package:`.
|
|
269
264
|
index.set(
|
|
270
265
|
canonicalKey(e.pkg ?? ownPackage, ownDocumentSystem(type), type, shortcode),
|
|
271
266
|
value,
|
|
@@ -387,11 +382,10 @@ export function resolveCrossNoteFigures(
|
|
|
387
382
|
* the one pass that resolves them.
|
|
388
383
|
* @returns {object} The resolver context.
|
|
389
384
|
*
|
|
390
|
-
* There is deliberately **no `manifestsComplete
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* caller still passing it is ignored rather than obeyed.
|
|
385
|
+
* There is deliberately **no `manifestsComplete`**: an unresolved cross-package
|
|
386
|
+
* address is never softened while a manifest is missing, since the pack
|
|
387
|
+
* compilers and the link checker do not soften it and one authored link must
|
|
388
|
+
* not get two verdicts.
|
|
395
389
|
*/
|
|
396
390
|
export function wikiContext(
|
|
397
391
|
built,
|
package/engine/site-preview.mjs
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
3
|
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
4
|
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
5
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
6
12
|
*/
|
|
7
13
|
|
package/engine/site-root.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
3
10
|
*
|
|
4
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
5
12
|
*/
|
|
@@ -216,7 +223,7 @@ export async function indexSite({ site, output }) {
|
|
|
216
223
|
* sits there. Left in place it would redirect the prefix root — the homepage —
|
|
217
224
|
* to an address nothing publishes. The same holds for a `pagefind/` left by an
|
|
218
225
|
* earlier build when `search` is off: deployed, it is a working search over
|
|
219
|
-
* pages that may
|
|
226
|
+
* pages that may not exist, so it goes too.
|
|
220
227
|
*
|
|
221
228
|
* `_headers` is written before the index is built, so a failed index leaves
|
|
222
229
|
* the root files as they would be — the failure is the indexer's, reported as
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
2
13
|
|
|
3
14
|
/** Relationships a being states from its own point of view. */
|
|
4
15
|
export const SOCIAL_TIES = Object.freeze([
|
package/engine/social-ties.mjs
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
6
|
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
7
|
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
8
11
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
9
12
|
*/
|
|
10
13
|
|
package/engine/sql-tables.mjs
CHANGED
|
@@ -569,7 +569,7 @@ function cellText(value, column) {
|
|
|
569
569
|
* ships a link that does not resolve. A `_section` alias splits the result into
|
|
570
570
|
* a headed table per distinct value, in the order the rows arrive — so the
|
|
571
571
|
* authored `ORDER BY` decides the section order too, and one query replaces the
|
|
572
|
-
* forty near-identical blocks
|
|
572
|
+
* forty near-identical blocks it would otherwise take.
|
|
573
573
|
*
|
|
574
574
|
* **A result selecting nothing still renders its header and rule.** The finding
|
|
575
575
|
* is the point, not withholding the output: an authored heading with an empty
|
|
@@ -677,6 +677,17 @@ export function renderSqlTable(
|
|
|
677
677
|
export async function prepareSqlTables(db, sources, { linkable, records } = {}) {
|
|
678
678
|
const prepared = new Map();
|
|
679
679
|
if (records) prepared.pageLists = preparePageLists(records, sources);
|
|
680
|
+
// The corpus does not change while a preparation runs, so a query written
|
|
681
|
+
// twice has one answer. Each use gets its own copy, because a caller
|
|
682
|
+
// rewrites the rows it is handed.
|
|
683
|
+
const answers = new Map();
|
|
684
|
+
const reader = {
|
|
685
|
+
...db,
|
|
686
|
+
async query(sql) {
|
|
687
|
+
if (!answers.has(sql)) answers.set(sql, await db.query(sql));
|
|
688
|
+
return structuredClone(answers.get(sql));
|
|
689
|
+
},
|
|
690
|
+
};
|
|
680
691
|
for (const { source, markdown } of sources) {
|
|
681
692
|
const blocks = findSqlBlocks(markdown);
|
|
682
693
|
if (!blocks.length) continue;
|
|
@@ -685,7 +696,7 @@ export async function prepareSqlTables(db, sources, { linkable, records } = {})
|
|
|
685
696
|
for (const block of blocks) {
|
|
686
697
|
try {
|
|
687
698
|
if (block.problems.length) throw new Error(block.problems.join("; "));
|
|
688
|
-
const result = await runSqlQuery(
|
|
699
|
+
const result = await runSqlQuery(reader, block.query);
|
|
689
700
|
forNote.push({
|
|
690
701
|
markdown: renderSqlTable(result, {
|
|
691
702
|
linkable,
|
|
@@ -810,9 +821,11 @@ export async function generatedSectionsFor(
|
|
|
810
821
|
* From {@link generatedSectionsFor}.
|
|
811
822
|
* @param {Array<{source: string, markdown: string, frontmatter?: object}>} sources -
|
|
812
823
|
* The bodies to answer, extended in place.
|
|
824
|
+
* @param {Map<string, {body: string, frontmatter: object|null}>} [parsed] -
|
|
825
|
+
* Notes already parsed, keyed by file; a note absent from it is read.
|
|
813
826
|
* @returns {void}
|
|
814
827
|
*/
|
|
815
|
-
export function joinGeneratedSections(generated, sources) {
|
|
828
|
+
export function joinGeneratedSections(generated, sources, parsed) {
|
|
816
829
|
for (const [absPath, text] of generated.sections) {
|
|
817
830
|
const source = sources.find((one) => one.source === absPath);
|
|
818
831
|
if (source) {
|
|
@@ -820,7 +833,7 @@ export function joinGeneratedSections(generated, sources) {
|
|
|
820
833
|
continue;
|
|
821
834
|
}
|
|
822
835
|
if (!generated.views.has(absPath)) continue;
|
|
823
|
-
const { body, frontmatter } = parseMarkdownFile(absPath);
|
|
836
|
+
const { body, frontmatter } = parsed?.get(absPath) ?? parseMarkdownFile(absPath);
|
|
824
837
|
sources.push({
|
|
825
838
|
source: absPath,
|
|
826
839
|
markdown: appendGeneratedSections(body, text),
|
|
@@ -847,6 +860,23 @@ export function attachGeneratedSections(prepared, generated) {
|
|
|
847
860
|
}
|
|
848
861
|
}
|
|
849
862
|
|
|
863
|
+
/**
|
|
864
|
+
* Each declared dependency's cached index, as {@link openNotesDatabase} attaches
|
|
865
|
+
* them. Empty where none has been fetched.
|
|
866
|
+
*
|
|
867
|
+
* @param {object} [config] - Resolved configuration, defaulting to ambient.
|
|
868
|
+
* @returns {Promise<Array<{id: string, file: string}>>} The dependencies.
|
|
869
|
+
*/
|
|
870
|
+
export async function cachedDependencies(config) {
|
|
871
|
+
try {
|
|
872
|
+
const { cachedMetadataIndexes } = await import("./metadata-index.mjs");
|
|
873
|
+
const { loadPackConfig } = await import("./pack-config.mjs");
|
|
874
|
+
return cachedMetadataIndexes(config ?? loadPackConfig());
|
|
875
|
+
} catch {
|
|
876
|
+
return [];
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
850
880
|
/**
|
|
851
881
|
* Answer every `sql` directive in a content tree.
|
|
852
882
|
*
|
|
@@ -871,6 +901,14 @@ export function attachGeneratedSections(prepared, generated) {
|
|
|
871
901
|
* @param {object[]} [opts.records] - Index records the caller already derived.
|
|
872
902
|
* A command that also builds a link index holds them already, and deriving
|
|
873
903
|
* them twice is the duplicated-corpus failure this closes.
|
|
904
|
+
* @param {Map<string, {body: string, frontmatter: object|null}>} [opts.parsed] -
|
|
905
|
+
* The notes {@link module:engine/content-index.indexRecordsFor} parsed for
|
|
906
|
+
* `records`, keyed by file, so no note is parsed twice. A note absent from it
|
|
907
|
+
* is read.
|
|
908
|
+
* @param {object} [opts.database] - An open notes database over `records`, from
|
|
909
|
+
* {@link openNotesDatabase} with this call's `audience` and the declared
|
|
910
|
+
* dependencies from {@link cachedDependencies}. It is queried and left open;
|
|
911
|
+
* absent, one is opened and closed here.
|
|
874
912
|
* @param {"all"|"public"} [opts.audience] - Whether to exclude GM notes.
|
|
875
913
|
* @param {Map<string, object>} [opts.foreignIndex] - The fetched indexes the
|
|
876
914
|
* generated sections take in, where the caller holds them already; absent,
|
|
@@ -881,21 +919,22 @@ export function attachGeneratedSections(prepared, generated) {
|
|
|
881
919
|
*/
|
|
882
920
|
export async function prepareTreeSqlTables(
|
|
883
921
|
contentBase,
|
|
884
|
-
{ config, skipDirectories, records, audience = "all", foreignIndex } = {},
|
|
922
|
+
{ config, skipDirectories, records, parsed, database, audience = "all", foreignIndex } = {},
|
|
885
923
|
) {
|
|
886
924
|
// Imported here rather than at module scope: the index reaches the pack
|
|
887
925
|
// compilers through `manifest-emit` → `journals`, so a static import from a
|
|
888
926
|
// module they load would close a cycle and leave `BasePackCompiler`
|
|
889
927
|
// uninitialised for whichever module the runtime happened to load first.
|
|
890
928
|
const { indexRecordsFor } = await import("./content-index.mjs");
|
|
929
|
+
const notes = parsed ?? new Map();
|
|
891
930
|
const indexRecords = (
|
|
892
|
-
records ?? indexRecordsFor({ contentBase, config, skipDirectories })
|
|
931
|
+
records ?? indexRecordsFor({ contentBase, config, skipDirectories, parsed: notes })
|
|
893
932
|
).filter((record) => audience !== "public" || !isGmNote(record));
|
|
894
933
|
|
|
895
934
|
// Which notes carry a directive, discovered over the same corpus every
|
|
896
935
|
// other pass reads rather than over a walk of this one's own. The
|
|
897
936
|
// body has to be read to find a fence — the index carries no note text —
|
|
898
|
-
// but *which files* to read is
|
|
937
|
+
// but *which files* to read is not a second answer.
|
|
899
938
|
//
|
|
900
939
|
// `parseMarkdownFile` yields the body `walkMarkdownTree` yielded, trimmed
|
|
901
940
|
// the same way, which matters: results are keyed by note and looked up by
|
|
@@ -905,7 +944,7 @@ export async function prepareTreeSqlTables(
|
|
|
905
944
|
for (const record of indexRecords) {
|
|
906
945
|
if (!isNoteRecord(record)) continue;
|
|
907
946
|
const absPath = noteFile(contentBase, record);
|
|
908
|
-
const { body, frontmatter } = parseMarkdownFile(absPath);
|
|
947
|
+
const { body, frontmatter } = notes.get(absPath) ?? parseMarkdownFile(absPath);
|
|
909
948
|
if (
|
|
910
949
|
body &&
|
|
911
950
|
(findSqlBlocks(body).length ||
|
|
@@ -953,7 +992,7 @@ export async function prepareTreeSqlTables(
|
|
|
953
992
|
);
|
|
954
993
|
if (!needsDatabase) {
|
|
955
994
|
const pageOnly = new Map();
|
|
956
|
-
joinGeneratedSections(derivedOnly, sources);
|
|
995
|
+
joinGeneratedSections(derivedOnly, sources, notes);
|
|
957
996
|
pageOnly.inline = new Map();
|
|
958
997
|
pageOnly.pageLists = preparePageLists(indexRecords, sources);
|
|
959
998
|
attachGeneratedSections(pageOnly, derivedOnly);
|
|
@@ -978,20 +1017,17 @@ export async function prepareTreeSqlTables(
|
|
|
978
1017
|
// same cycle reason the index is, and tolerated when absent: a tree with no
|
|
979
1018
|
// `sql` directive never reaches this line, and one whose dependency has not
|
|
980
1019
|
// been fetched already fails earlier with a message naming the fetch.
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
dependencies = [];
|
|
988
|
-
}
|
|
989
|
-
const db = await openNotesDatabase(indexRecords, { dependencies, audience });
|
|
1020
|
+
const db =
|
|
1021
|
+
database ??
|
|
1022
|
+
(await openNotesDatabase(indexRecords, {
|
|
1023
|
+
dependencies: await cachedDependencies(config),
|
|
1024
|
+
audience,
|
|
1025
|
+
}));
|
|
990
1026
|
try {
|
|
991
1027
|
const generated = await generatedSectionsFor(db, indexRecords, contentBase, {
|
|
992
1028
|
derived: derivedOnly.derived,
|
|
993
1029
|
});
|
|
994
|
-
joinGeneratedSections(generated, sources);
|
|
1030
|
+
joinGeneratedSections(generated, sources, notes);
|
|
995
1031
|
const prepared = await prepareSqlTables(db, sources, {
|
|
996
1032
|
linkable: (ref) => addresses.has(renderAddress(ref)),
|
|
997
1033
|
records: indexRecords,
|
|
@@ -1000,6 +1036,6 @@ export async function prepareTreeSqlTables(
|
|
|
1000
1036
|
attachGeneratedSections(prepared, generated);
|
|
1001
1037
|
return prepared;
|
|
1002
1038
|
} finally {
|
|
1003
|
-
await db.close();
|
|
1039
|
+
if (!database) await db.close();
|
|
1004
1040
|
}
|
|
1005
1041
|
}
|
|
@@ -36,7 +36,8 @@ export const STANDING_BODY_TYPES = Object.freeze(["affiliation"]);
|
|
|
36
36
|
*
|
|
37
37
|
* `rank` is the level on the body's own `governance.ranks` ladder, and every
|
|
38
38
|
* membership states one. `office` is a key of its `governance.offices` map, and
|
|
39
|
-
* is stated only where the being holds a post.
|
|
39
|
+
* is stated only where the being holds a post. `societies` names the societies
|
|
40
|
+
* the membership is held in, as a list because a membership can span several.
|
|
40
41
|
*
|
|
41
42
|
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
42
43
|
*/
|
|
@@ -56,6 +57,15 @@ export const STANDING_FIELDS = Object.freeze([
|
|
|
56
57
|
shape: "a post from that body's `governance.offices`",
|
|
57
58
|
describe: "A post the body names in its `governance.offices`, where one is held.",
|
|
58
59
|
}),
|
|
60
|
+
Object.freeze({
|
|
61
|
+
name: "societies",
|
|
62
|
+
kind: "list",
|
|
63
|
+
shape: "a list of society names",
|
|
64
|
+
entries: Object.freeze({ kind: "string", shape: "a string" }),
|
|
65
|
+
describe:
|
|
66
|
+
"The societies the membership is held in, by name. SoHL carries one, " +
|
|
67
|
+
"the first, as the affiliation item's `system.society`.",
|
|
68
|
+
}),
|
|
59
69
|
]);
|
|
60
70
|
|
|
61
71
|
/** The keys of {@link STANDING_FIELDS}, in declared order. */
|