@heroiclands/package-build 22.14.1 → 22.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/content-build.mjs +6 -6
  3. package/bin/package-build.mjs +10 -0
  4. package/config.mjs +18 -1
  5. package/content-config.mjs +2 -2
  6. package/docs/api.md +63 -35
  7. package/docs/configuration.md +16 -7
  8. package/docs/content-format.md +157 -181
  9. package/docs/getting-started.md +42 -11
  10. package/docs/project-setup.md +5 -6
  11. package/engine/address.mjs +3 -5
  12. package/engine/asset-bindings.mjs +66 -0
  13. package/engine/calendar-notes.mjs +74 -0
  14. package/engine/changelog-lint.mjs +2 -2
  15. package/engine/content-index.mjs +63 -36
  16. package/engine/content-lint.mjs +1 -2
  17. package/engine/content-package.mjs +2 -2
  18. package/engine/content-tables.mjs +4 -8
  19. package/engine/folder-notes.mjs +1 -2
  20. package/engine/foundry-entries.mjs +21 -6
  21. package/engine/frontmatter-lint.mjs +3 -4
  22. package/engine/frontmatter.mjs +5 -8
  23. package/engine/helpers.mjs +3 -4
  24. package/engine/index.mjs +3 -0
  25. package/engine/infobox.mjs +67 -2
  26. package/engine/item-registry.mjs +2 -2
  27. package/engine/note-vocabulary.mjs +92 -6
  28. package/engine/pack-config.mjs +2 -2
  29. package/engine/pack-router.mjs +2 -2
  30. package/engine/pdf-render.mjs +1 -1
  31. package/engine/pdf-toc.mjs +3 -3
  32. package/engine/retired-fields.mjs +1 -1
  33. package/engine/scene-levels.mjs +4 -4
  34. package/engine/schema-check.mjs +1 -1
  35. package/engine/site-build.mjs +134 -121
  36. package/engine/site-preview.mjs +293 -0
  37. package/engine/site-root.mjs +20 -2
  38. package/engine/social-tie-terms.mjs +11 -0
  39. package/engine/social-ties.mjs +84 -0
  40. package/engine/sql-tables.mjs +4 -9
  41. package/hm3/actors.mjs +1 -1
  42. package/package.json +3 -3
  43. package/sohl/item-fields.mjs +2 -2
  44. package/types/config.d.mts +8 -0
  45. package/types/content-config.d.mts +1 -1
  46. package/types/engine/address.d.mts +3 -5
  47. package/types/engine/asset-bindings.d.mts +6 -0
  48. package/types/engine/calendar-notes.d.mts +12 -0
  49. package/types/engine/content-index.d.mts +11 -0
  50. package/types/engine/content-package.d.mts +2 -2
  51. package/types/engine/folder-notes.d.mts +1 -2
  52. package/types/engine/frontmatter-lint.d.mts +1 -2
  53. package/types/engine/frontmatter.d.mts +2 -4
  54. package/types/engine/helpers.d.mts +3 -4
  55. package/types/engine/index.d.mts +1 -0
  56. package/types/engine/item-registry.d.mts +2 -2
  57. package/types/engine/note-vocabulary.d.mts +5 -6
  58. package/types/engine/pack-config.d.mts +1 -1
  59. package/types/engine/pack-router.d.mts +1 -1
  60. package/types/engine/pdf-render.d.mts +1 -1
  61. package/types/engine/pdf-toc.d.mts +1 -1
  62. package/types/engine/site-build.d.mts +36 -3
  63. package/types/engine/site-preview.d.mts +34 -0
  64. package/types/engine/social-tie-terms.d.mts +5 -0
  65. package/types/engine/social-ties.d.mts +10 -0
@@ -0,0 +1,84 @@
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
+ * SPDX-License-Identifier: GPL-3.0-or-later
9
+ */
10
+
11
+ import { parseAddress, renderAddress } from "./address.mjs";
12
+ import { positionOfFrontmatterPath } from "./diagnostics.mjs";
13
+ import { SOCIAL_TIES } from "./social-tie-terms.mjs";
14
+
15
+ export { SOCIAL_TIES };
16
+
17
+ const TERMS = new Set(SOCIAL_TIES.map(({ term }) => term));
18
+ const ACCEPTED = new Set(["being", "affiliation"]);
19
+
20
+ /** Validate a being's map of defining relationships. */
21
+ export function checkSocialTies(note, { index } = {}) {
22
+ const ties = note.fm?.data?.socialTies;
23
+ if (ties === undefined || ties === null) return [];
24
+ const at = (key) => ({
25
+ file: note.file,
26
+ ...positionOfFrontmatterPath(
27
+ note.raw ?? "",
28
+ key ? ["data", "socialTies", key] : ["data", "socialTies"],
29
+ { key: true },
30
+ ),
31
+ severity: "error",
32
+ });
33
+ if (typeof ties !== "object" || Array.isArray(ties))
34
+ return [
35
+ {
36
+ ...at(),
37
+ message: "data.socialTies must be a map of Address keys to relationship terms",
38
+ },
39
+ ];
40
+ const findings = [];
41
+ for (const [key, term] of Object.entries(ties)) {
42
+ const tuple = parseAddress(
43
+ key,
44
+ {
45
+ package: index?.contentPackage ?? note.fm?.package,
46
+ system: "note",
47
+ types: index?.types,
48
+ packages: index?.packages,
49
+ },
50
+ { declared: true },
51
+ );
52
+ if (tuple.reason) {
53
+ findings.push({
54
+ ...at(key),
55
+ message: `data.socialTies key ${JSON.stringify(key)} is not a complete Address (${tuple.reason}); write being or affiliation as its type`,
56
+ });
57
+ } else if (!ACCEPTED.has(tuple.type)) {
58
+ findings.push({
59
+ ...at(key),
60
+ message: `data.socialTies accepts being or affiliation, not ${tuple.type}`,
61
+ });
62
+ } else if (
63
+ tuple.package === (index?.contentPackage ?? note.fm?.package) &&
64
+ tuple.type === "being" &&
65
+ tuple.shortcode === note.fm?.shortcode
66
+ ) {
67
+ findings.push({
68
+ ...at(key),
69
+ message: `data.socialTies cannot address this being itself`,
70
+ });
71
+ } else if (index?.addressHit && !index.addressHit(renderAddress(tuple))) {
72
+ findings.push({
73
+ ...at(key),
74
+ message: `data.socialTies target ${renderAddress(tuple)} does not resolve`,
75
+ });
76
+ }
77
+ if (!TERMS.has(term))
78
+ findings.push({
79
+ ...at(key),
80
+ message: `data.socialTies value ${JSON.stringify(term)} must be one of ${SOCIAL_TIES.map(({ term: value }) => value).join(", ")}`,
81
+ });
82
+ }
83
+ return findings;
84
+ }
@@ -14,15 +14,8 @@
14
14
  /**
15
15
  * Content tables written in SQL, queried over the content index.
16
16
  *
17
- * The tables were written in Dataview's query language, chosen when the corpus
18
- * lived in an Obsidian vault so a table rendered live while authoring. The vault
19
- * is gone, and what remained was a hand-written parser and evaluator for someone
20
- * else's language, kept faithful to semantics nothing checked it against.
21
- *
22
- * **The query is real SQL, run by DuckDB** — not a dialect maintained here. That
23
- * is the whole point: a partial reimplementation would accept some valid SQL and
24
- * silently misread the rest, which is worse than an unfamiliar language, because
25
- * the boundary is invisible.
17
+ * **The query is SQL, run by DuckDB.** Its syntax and evaluation follow the
18
+ * database rather than a separate parser in the toolchain.
26
19
  *
27
20
  * **What SQL cannot say, the projection says.** Rendering a table is not a
28
21
  * relational operation: which column links, and where a section breaks, are
@@ -273,6 +266,8 @@ export async function openNotesDatabase(records, { dir, dependencies = [], addre
273
266
  };
274
267
  },
275
268
  async close() {
269
+ connection.closeSync();
270
+ instance.closeSync();
276
271
  if (!dir) fs.rmSync(base, { recursive: true, force: true });
277
272
  },
278
273
  };
package/hm3/actors.mjs CHANGED
@@ -170,7 +170,7 @@ function defaultActorImg(subType) {
170
170
  * and `HM3.defaultPsionicsIconName` in the system's own `config.js` — so a
171
171
  * compiled item looks like one created in the client. `weapongear` and
172
172
  * `missilegear` need no row here: a reference spelled either resolves through
173
- * `HM3_DEFAULT_ITEM_ART`'s own `weapongear` key (see #582), and every
173
+ * `HM3_DEFAULT_ITEM_ART`'s own `weapongear` key, and every
174
174
  * predefined weapon and missile in HM3's catalogue carries its own `img`.
175
175
  *
176
176
  * @type {Readonly<Record<string, string>>}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.14.1",
3
+ "version": "22.14.3",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -147,10 +147,10 @@
147
147
  "loglevel": "^1.9.2",
148
148
  "loglevel-plugin-prefix": "^0.8.4",
149
149
  "markdown-it": "^15.0.0",
150
- "markdownlint-cli2": "^0.23.2",
150
+ "markdownlint-cli2": "^0.23.3",
151
151
  "pagefind": "^1.5.2",
152
152
  "prettier": "^3.9.6",
153
- "smol-toml": "^1.7.0",
153
+ "smol-toml": "^1.8.0",
154
154
  "ssh2-sftp-client": "^12.1.1",
155
155
  "typescript": "^6.0.3",
156
156
  "unidecode": "^1.1.0",
@@ -140,7 +140,7 @@ const DURATION_BASE = Object.freeze({
140
140
 
141
141
  /** Aptitude weights per skill selector, validated as whole numbers. */
142
142
  const SKILL_APTITUDES = Object.freeze({
143
- shape: "map of skill selector → whole number",
143
+ shape: "map of skill selector > whole number",
144
144
  read: (_raw, { fm }) => resolveSkillAptitudes(fm, noteContext(fm, "mystery")),
145
145
  });
146
146
 
@@ -188,7 +188,7 @@ const SKILL_UUID_ITEMS = Object.freeze({
188
188
 
189
189
  /** Standings toward other affiliations, validated against the closed list. */
190
190
  const RELATION = Object.freeze({
191
- shape: "map of affiliation address → standing",
191
+ shape: "map of affiliation address > standing",
192
192
  read: (_raw, { fm }) => resolveRelation(fm, noteContext(fm, "affiliation")),
193
193
  });
194
194
 
@@ -179,6 +179,14 @@ export type AssetSpec = {
179
179
  * Destination, relative to the staged package root.
180
180
  */
181
181
  to: string;
182
+ /**
183
+ * The foreign Address each file's basename claims, without its shortcode.
184
+ */
185
+ bindsTo?: {
186
+ package: string;
187
+ system: string;
188
+ type: string;
189
+ } | undefined;
182
190
  };
183
191
  /**
184
192
  * The resolved `packageBuild` section, every optional half filled in.
@@ -781,7 +781,7 @@ export type ContentBuildConfig = {
781
781
  * Derived: the default art
782
782
  * of each entry that paired one. Sparse — a
783
783
  * type absent here has no default, and a note
784
- * of it must carry `img:` (#7).
784
+ * of it must carry `img:`.
785
785
  */
786
786
  itemArt: Readonly<Record<string, string>>;
787
787
  /**
@@ -243,11 +243,9 @@ export function readCanonicalKey(key: unknown): {
243
243
  * confidently they mark a value as qualified:
244
244
  *
245
245
  * - **`type-shortcode`** and its qualified forms — the canonical spelling.
246
- * Obsidian reads `/` inside a wikilink as a *path* and resolves it against the
247
- * vault's folders, so a slash-qualified link is a broken link in the editor
248
- * where the content is authored.
249
- * - **`type/shortcode`** — resolved so that a link written before the vault
250
- * migrated does not silently die. A slash is *unconditionally* a qualifier:
246
+ * The hyphenated form is portable across Markdown editors and renderers.
247
+ * - **`type/shortcode`** — an accepted qualified spelling. A slash is
248
+ * *unconditionally* a qualifier:
251
249
  * nothing else uses one, so an unknown type before it is reported rather than
252
250
  * guessed at. The split is at the **last** slash.
253
251
  *
@@ -0,0 +1,6 @@
1
+ /** Check configured foreign Address bindings against fetched indexes. */
2
+ export function checkForeignAssetBindings(config: any, { foreignIndex, assets }?: {}): {
3
+ file: string;
4
+ severity: string;
5
+ message: string;
6
+ }[];
@@ -18,6 +18,14 @@ export function worldInvariants(index?: object): {
18
18
  yearNote: object | null;
19
19
  moonNote: object | null;
20
20
  };
21
+ /** Check each authored format slot and every unescaped letter run in it. */
22
+ export function checkCalendarDateFormats(note: any): {
23
+ severity: any;
24
+ message: any;
25
+ line?: number;
26
+ column?: number;
27
+ file: any;
28
+ }[];
21
29
  /**
22
30
  * Check a `place` note's world facts.
23
31
  *
@@ -150,6 +158,10 @@ export const CALENDAR_SUBTYPE: "calendar";
150
158
  * @type {readonly string[]}
151
159
  */
152
160
  export const INVARIANT_SUBTYPES: readonly string[];
161
+ /** Calendaria's display locations that a calendar note can format. */
162
+ export const CALENDAR_DATE_FORMAT_KEYS: readonly string[];
163
+ /** Bare date tokens Calendaria substitutes inside authored format strings. */
164
+ export const CALENDAR_FORMAT_TOKENS: Readonly<Set<string>>;
153
165
  /**
154
166
  * The `data:` keys a calendar note may write — the closed list, in authored
155
167
  * order.
@@ -121,6 +121,17 @@ export function buildIndexRecord({ frontmatter, relPath, absPath, contentPackage
121
121
  bodyLine?: number | undefined;
122
122
  manifest?: object | undefined;
123
123
  }): Record<string, any>;
124
+ /** Derive every index record belonging to one parsed note. */
125
+ export function indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext, }: {
126
+ frontmatter: any;
127
+ body: any;
128
+ bodyLine: any;
129
+ relPath: any;
130
+ absPath: any;
131
+ contentPackage: any;
132
+ manifest: any;
133
+ addressContext: any;
134
+ }): object[];
124
135
  /**
125
136
  * Read a content tree into index records, in the order they will be written.
126
137
  *
@@ -20,7 +20,7 @@
20
20
  * package below would differ.
21
21
  *
22
22
  * An accessor rather than a hoisted constant, so that importing this module
23
- * needs no configuration (#2).
23
+ * needs no configuration.
24
24
  *
25
25
  * @returns {string} The configured `contentPackage`.
26
26
  */
@@ -42,7 +42,7 @@ export function contentPackage(): string;
42
42
  * written — fails the build if this value and the manifest's `id` ever drift.
43
43
  *
44
44
  * An accessor rather than a hoisted constant, so that importing this module
45
- * needs no configuration (#2).
45
+ * needs no configuration.
46
46
  *
47
47
  * @returns {string} The configured `foundryPackage`.
48
48
  */
@@ -2,8 +2,7 @@
2
2
  * An authored `parent` with any wikilink brackets and label stripped.
3
3
  *
4
4
  * The specification types `parent` as an `Address`, written bare — but
5
- * `[[address]]` is what an author reaches for,
6
- * and Obsidian wrote that form for years. Accepting both costs one regex and
5
+ * `[[address]]` is also a recognizable link form. Accepting both costs one regex and
7
6
  * removes a failure whose message would have to explain the difference.
8
7
  *
9
8
  * @param {string|null} value - As authored.
@@ -3,8 +3,7 @@
3
3
  *
4
4
  * Deliberately lenient about the spellings YAML makes ambiguous: `"12"` is a
5
5
  * number, because a quoted scalar is how a number arrives from many editors,
6
- * and a map authored as an empty list is a map, because Obsidian's property
7
- * editor serialises an emptied map that way (#8). What it rejects is a value
6
+ * and a map authored as an empty list has no entries, just like `{}`. What it rejects is a value
8
7
  * that cannot mean what the field is for — `weight: heavy`.
9
8
  *
10
9
  * @param {unknown} value - The authored value.
@@ -89,8 +89,7 @@ export function resolveCharges(fm: object): {
89
89
  * kept — an element a sign leaves untouched still beats one another sign
90
90
  * hinders, so it carries real weight when maps merge.
91
91
  *
92
- * An absent property, an empty map, and the empty **list** Obsidian's property
93
- * editor writes for a cleared map all mean the same thing — see
92
+ * An absent property, an empty map, and an empty list all mean the same thing — see
94
93
  * {@link readMapEntries}.
95
94
  *
96
95
  * @param {object} fm - The item frontmatter.
@@ -108,8 +107,7 @@ export function resolveSkillAptitudes(fm: object, ctx?: string): Record<string,
108
107
  * and be dropped silently, shipping an affiliation whose authored hostility had
109
108
  * quietly become neutrality — so it is a build error instead.
110
109
  *
111
- * An absent property, an empty map, and the empty **list** Obsidian's property
112
- * editor writes for a cleared map all mean the same thing — neutral toward
110
+ * An absent property, an empty map, and an empty list all mean the same thing — neutral toward
113
111
  * everyone. See {@link readMapEntries}.
114
112
  *
115
113
  * @param {object} fm - The item frontmatter.
@@ -72,9 +72,8 @@ export function assertSuppliedCorpus(records: readonly object[] | undefined, who
72
72
  * is warned about and skipped.
73
73
  *
74
74
  * Directory names in `skipDirectories` are ignored wherever they appear. The
75
- * walk itself knows nothing about what they mean: `Templates/` is an Obsidian
76
- * templater convention this repository's vault happens to use, not a property
77
- * of a content tree, so it is stated by the caller rather than hard-coded.
75
+ * walk itself knows nothing about what they mean: `Templates/` is authoring
76
+ * scaffolding rather than content, so the caller names it explicitly.
78
77
  *
79
78
  * @param {string} rootDir - Root of the tree to walk.
80
79
  * @param {object} opts
@@ -319,7 +318,7 @@ export function statsForPack(packSystem: string | null | undefined, config?: obj
319
318
  * Each compiler used to hoist `const STATS = buildStats()` at module scope,
320
319
  * which read the shipped package manifest the moment the module was imported —
321
320
  * so importing a compiler required a manifest to exist even when nothing was
322
- * going to be compiled (#2). Deferred to first use and memoised here, the
321
+ * going to be compiled. Deferred to first use and memoised here, the
323
322
  * cost and the identity are what they always were; only the moment moved.
324
323
  *
325
324
  * @returns {object} The default `_stats` block, shared by every compiler.
@@ -49,6 +49,7 @@ export * as assetIndex from "./asset-index.mjs";
49
49
  export * as packages from "./packages.mjs";
50
50
  export * as packagebuildIndex from "./packagebuild-index.mjs";
51
51
  export * as siteBuild from "./site-build.mjs";
52
+ export * as sitePreview from "./site-preview.mjs";
52
53
  export * as contentLint from "./content-lint.mjs";
53
54
  export * as contentCharset from "./content-charset.mjs";
54
55
  export * as contentIcons from "./content-icons.mjs";
@@ -11,7 +11,7 @@
11
11
  * compile.
12
12
  *
13
13
  * An accessor rather than a hoisted constant, so that importing this module
14
- * needs no configuration (#2).
14
+ * needs no configuration.
15
15
  *
16
16
  * @returns {ReadonlySet<string>} The configured item types.
17
17
  */
@@ -54,7 +54,7 @@ export function itemFields(type: string, system?: string): readonly object[] | u
54
54
  * SoHL-owned table could never hold. Art is not looked up in
55
55
  * `sohl/default-item-art.mjs` instead: a type was configurable while its
56
56
  * default art was not, so a second consumer's items compiled only if every one
57
- * of its notes set `img:` (#7).
57
+ * of its notes set `img:`.
58
58
  *
59
59
  * **Still fail-fast.** A type with neither a note-level `img:` nor paired art
60
60
  * aborts the pack build rather than shipping a mismatched icon — the contract
@@ -36,14 +36,13 @@ export function exclusiveTagGroups(type: string, groups?: object): {
36
36
  /**
37
37
  * Whether a note carries a given tag, however the author wrote it.
38
38
  *
39
- * `tags:` is authored by hand and Obsidian is permissive about it: a single tag
39
+ * `tags:` accepts common YAML forms: a single tag
40
40
  * may be a scalar rather than a list, a value may carry the leading `#` it is
41
41
  * written with in prose, and case and surrounding space are not significant.
42
42
  * The spelling of the tag *itself* still is — a near miss is a near miss, and
43
43
  * the frontmatter lint is what reports it; nothing here guesses.
44
44
  *
45
- * Reads `tags` and, as Dataview does, `tag` — the singular spelling Obsidian
46
- * also accepts.
45
+ * Reads `tags` and the singular `tag` spelling.
47
46
  *
48
47
  * @param {object|null|undefined} fm - Parsed frontmatter.
49
48
  * @param {string} tag - The tag to look for, in its declared spelling.
@@ -279,15 +278,15 @@ export type DataFieldSpec = {
279
278
  * value — which is the honest answer wherever the specification's stated
280
279
  * shape and the shape notes are authored in today disagree.
281
280
  */
282
- kind?: "string" | "number" | "boolean" | "map" | "list" | "scalar-or-map" | "address" | "shortcode" | undefined;
281
+ kind?: "string" | "number" | "boolean" | "map" | "list" | "shortcode" | "scalar-or-map" | "address" | undefined;
283
282
  /**
284
283
  * - Type of each list or pack-map value.
285
284
  */
286
- entryKind?: "address" | "shortcode" | undefined;
285
+ entryKind?: "shortcode" | "address" | undefined;
287
286
  /**
288
287
  * - Type of each map key.
289
288
  */
290
- keyKind?: "address" | "shortcode" | undefined;
289
+ keyKind?: "shortcode" | "address" | undefined;
291
290
  /**
292
291
  * - An alternative `subType:<skill-subtype>` key.
293
292
  */
@@ -105,7 +105,7 @@ export function configFromData(data: unknown, configPath: string): import("../co
105
105
  *
106
106
  * Every engine module that needs a configured value calls this, rather than
107
107
  * hoisting one at import: that is what keeps the library importable without a
108
- * configuration (#2). The result is memoised, so calling it in a default
108
+ * configuration. The result is memoised, so calling it in a default
109
109
  * parameter — the usual spelling here — costs one property read per call.
110
110
  *
111
111
  * @returns {import("../content-config.mjs").ContentBuildConfig} The frozen configuration.
@@ -45,7 +45,7 @@ export function routerFor(config: object): ReturnType<typeof createPackRouter>;
45
45
  * asks where a note's document lives.
46
46
  *
47
47
  * An accessor rather than a hoisted constant, so that importing this module
48
- * needs no configuration (#2). {@link routerFor} keeps one router per
48
+ * needs no configuration. {@link routerFor} keeps one router per
49
49
  * configuration object, so repeated calls return the same instance.
50
50
  *
51
51
  * @returns {ReturnType<typeof createPackRouter>} This repository's router.
@@ -111,7 +111,7 @@ export function bookTypstPreamble(): string;
111
111
  /**
112
112
  * The whole book, as one Typst document.
113
113
  *
114
- * **Pure, and that is the point.** Everything a reviewer of #316 has to check
114
+ * **Pure, and that is the point.** The book's structural properties
115
115
  * about structure — the outline's shape, the anchors, which links went inward,
116
116
  * the order entries print in — is decided here from a plan and a map of bodies,
117
117
  * with no filesystem and no compiler. {@link module:engine/pdf-build} supplies
@@ -67,7 +67,7 @@ export function runTreeFilters(nodes: object[], db: {
67
67
  * The plan is an ordered list of entries — `section`, `prose`, `note` — and it
68
68
  * is the artifact worth having. Order, depth, the outline, the table of
69
69
  * contents, anchor uniqueness and every link destination are all readable from
70
- * it, so nearly the whole of what #316 asks for can be asserted here, on data,
70
+ * it, so the book's structure can be asserted here, on data,
71
71
  * without a renderer or a PDF. Only how the result *looks* needs eyes.
72
72
  *
73
73
  * **Notes are sorted, prose is not.** A section's entries come out in
@@ -141,9 +141,8 @@ export function tableUniverse(pages: object[]): Map<string, object[]>;
141
141
  * The frontmatter a page publishes with.
142
142
  *
143
143
  * An authored `aliases` is retired and refused before a build reaches
144
- * here, which makes this a guard rather than a working path. It was Obsidian's
145
- * — a list of *names* a reader might call
146
- * the note, which is vault addressing and stays in the vault. Hugo reads
144
+ * here, which makes this a guard rather than a working path. It is a list of
145
+ * names a reader might call the note. Hugo reads
147
146
  * `aliases` as **URL redirects**, so passing them through would publish a
148
147
  * redirect stub at each name. They are dropped, and this build emits no
149
148
  * redirects of its own.
@@ -228,6 +227,38 @@ export function pageFrontmatter(page: object, { decorate, webSrc, artSrc }: {
228
227
  export function pageDestination(page: object, { bundle }?: {
229
228
  bundle?: boolean | undefined;
230
229
  }): string;
230
+ /** Transform one content page without writing the site mount. */
231
+ export function renderSitePage(page: any, { index, foreign, universe, pass, decorate, linkable, sqlTables, config, artIndex, }: {
232
+ index: any;
233
+ foreign: any;
234
+ universe: any;
235
+ pass?: {} | undefined;
236
+ decorate: any;
237
+ linkable?: ((d: any) => boolean) | undefined;
238
+ sqlTables: any;
239
+ config: any;
240
+ artIndex: any;
241
+ }): {
242
+ page: any;
243
+ body: string;
244
+ data: object;
245
+ resolved: any[];
246
+ tableErrors: {
247
+ source: string;
248
+ directive: string;
249
+ reason: string;
250
+ line: number;
251
+ column?: number;
252
+ }[];
253
+ secretErrors: {
254
+ file: any;
255
+ line: number;
256
+ column: number;
257
+ message: string;
258
+ }[];
259
+ wikiErrors: any[];
260
+ imageErrors: any[];
261
+ };
231
262
  /**
232
263
  * Renders and writes every page.
233
264
  *
@@ -289,6 +320,8 @@ export function renderPages(pages: object[], options: object): {
289
320
  export function resolveSitePass(name: string | undefined, options: object): {
290
321
  beforeLinks?: Function;
291
322
  };
323
+ /** The site frontmatter decoration shared by the site writer and page preview. */
324
+ export function sitePageDecorator(config: any, index: any): (data: any, page: any) => void;
292
325
  /**
293
326
  * Builds a Hugo content tree from a content tree, and reports what it found.
294
327
  *
@@ -0,0 +1,34 @@
1
+ /** Prepare saved project state once, then render live text without writing a site. */
2
+ export function prepareSitePreview({ config }?: {
3
+ config?: import("../content-config.mjs").ContentBuildConfig | undefined;
4
+ }): Promise<{
5
+ /** Render the active buffer. Other local and foreign notes remain saved. */
6
+ render(file: any, text: any): Promise<{
7
+ ok: boolean;
8
+ findings: {
9
+ file: string;
10
+ severity: string;
11
+ message: string;
12
+ }[];
13
+ markdown?: undefined;
14
+ frontmatter?: undefined;
15
+ } | {
16
+ ok: boolean;
17
+ findings: {
18
+ path: string;
19
+ link: string;
20
+ file: string;
21
+ severity: string;
22
+ }[];
23
+ markdown?: undefined;
24
+ frontmatter?: undefined;
25
+ } | {
26
+ ok: boolean;
27
+ markdown: string;
28
+ frontmatter: any;
29
+ findings: never[];
30
+ }>;
31
+ /** Re-read saved notes and dependency indexes after a save. */
32
+ refresh(): Promise<void>;
33
+ close(): Promise<void>;
34
+ }>;
@@ -0,0 +1,5 @@
1
+ /** Relationships a being states from its own point of view. */
2
+ export const SOCIAL_TIES: readonly {
3
+ term: string;
4
+ meaning: string;
5
+ }[];
@@ -0,0 +1,10 @@
1
+ /** Validate a being's map of defining relationships. */
2
+ export function checkSocialTies(note: any, { index }?: {}): {
3
+ message: string;
4
+ severity: string;
5
+ line?: number;
6
+ column?: number;
7
+ file: any;
8
+ }[];
9
+ export { SOCIAL_TIES };
10
+ import { SOCIAL_TIES } from "./social-tie-terms.mjs";