@heroiclands/package-build 22.15.1 → 22.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +39 -678
  3. package/bin/{content-build.mjs → content-commands.mjs} +197 -118
  4. package/bin/package-build.mjs +315 -42
  5. package/bin/report.mjs +1 -1
  6. package/ci/ci-docker.mjs +6 -3
  7. package/ci/ci-steps.mjs +6 -4
  8. package/ci/content-format-reference.mjs +106 -0
  9. package/config.mjs +22 -2
  10. package/content-config.mjs +74 -134
  11. package/docs/api.md +296 -236
  12. package/docs/authoring/assets.md +31 -0
  13. package/docs/authoring/dates-and-calendars.md +85 -0
  14. package/docs/authoring/first-note.md +48 -0
  15. package/docs/authoring/frontmatter.md +40 -0
  16. package/docs/authoring/links-and-markup.md +137 -0
  17. package/docs/commands.md +633 -162
  18. package/docs/configuration.md +176 -140
  19. package/docs/content-format.md +50 -4229
  20. package/docs/design.md +32 -0
  21. package/docs/diagnostics.md +29 -22
  22. package/docs/getting-started.md +21 -877
  23. package/docs/guides/book.md +153 -0
  24. package/docs/guides/e2e.md +143 -0
  25. package/docs/guides/icons.md +126 -0
  26. package/docs/guides/localization.md +120 -0
  27. package/docs/guides/module-project.md +915 -0
  28. package/docs/guides/packs.md +167 -0
  29. package/docs/guides/reference-pages.md +148 -0
  30. package/docs/guides/release-and-deploy.md +114 -0
  31. package/docs/guides/site.md +152 -0
  32. package/docs/guides/system-project.md +45 -0
  33. package/docs/index.md +16 -0
  34. package/docs/project-setup.md +54 -35
  35. package/docs/reference/format-details.md +4362 -0
  36. package/docs/reference/note-types.md +532 -0
  37. package/engine/address-render.mjs +26 -0
  38. package/engine/address.mjs +3 -20
  39. package/engine/anchors.mjs +15 -6
  40. package/engine/asset-bindings.mjs +6 -2
  41. package/engine/base-compiler.mjs +47 -11
  42. package/engine/being-measurements.mjs +70 -0
  43. package/engine/calendar-artifacts.mjs +1 -1
  44. package/engine/calendar-choice.mjs +32 -0
  45. package/engine/calendar-format.mjs +441 -0
  46. package/engine/calendar-human.mjs +133 -0
  47. package/engine/calendar-notes.mjs +307 -88
  48. package/engine/calendars.mjs +8 -1
  49. package/engine/changelog-lint.mjs +2 -2
  50. package/engine/code-fences.mjs +23 -97
  51. package/engine/compile-corpus.mjs +6 -7
  52. package/engine/content-admonitions.mjs +114 -0
  53. package/engine/content-captions.mjs +181 -0
  54. package/engine/content-embeds.mjs +2 -2
  55. package/engine/content-footnotes.mjs +34 -0
  56. package/engine/content-format-check.mjs +2 -2
  57. package/engine/content-format.mjs +127 -73
  58. package/engine/content-format.yaml +746 -0
  59. package/engine/content-html.mjs +1 -1
  60. package/engine/content-icons.mjs +45 -92
  61. package/engine/content-images.mjs +22 -74
  62. package/engine/content-index.mjs +12 -3
  63. package/engine/content-links.mjs +21 -26
  64. package/engine/content-lint.mjs +1 -5
  65. package/engine/content-secrets.mjs +3 -1
  66. package/engine/content-tables.mjs +47 -1257
  67. package/engine/culture-choice.mjs +32 -0
  68. package/engine/date-conversion.mjs +47 -0
  69. package/engine/declaration-check.mjs +87 -0
  70. package/engine/docs-checks.mjs +140 -0
  71. package/engine/documentation-links.mjs +31 -0
  72. package/engine/exported-scene.mjs +1 -1
  73. package/engine/extension-attributes.mjs +74 -0
  74. package/engine/field-reference.mjs +1 -1
  75. package/engine/field-spec.mjs +4 -0
  76. package/engine/foreign-catalog.mjs +5 -5
  77. package/engine/format-generated.mjs +15 -0
  78. package/engine/foundry-entries.mjs +3 -1
  79. package/engine/frontmatter-lint.mjs +5 -1
  80. package/engine/frontmatter.mjs +10 -0
  81. package/engine/generate.mjs +2 -2
  82. package/engine/helpers.mjs +71 -117
  83. package/engine/homepage.mjs +2 -3
  84. package/engine/index-records.mjs +1 -0
  85. package/engine/index.mjs +18 -3
  86. package/engine/infobox-registry.mjs +3 -1
  87. package/engine/infobox.mjs +38 -20
  88. package/engine/item-compiler.mjs +10 -8
  89. package/engine/item-docs.mjs +7 -6
  90. package/engine/journals.mjs +51 -10
  91. package/engine/macros.mjs +1 -1
  92. package/engine/map-build.mjs +1 -1
  93. package/engine/map-dot.mjs +1 -1
  94. package/engine/markdown-expressions.mjs +118 -0
  95. package/engine/metadata-index.mjs +7 -3
  96. package/engine/note-addresses.mjs +1 -0
  97. package/engine/note-dates.mjs +314 -64
  98. package/engine/note-package.mjs +5 -25
  99. package/engine/note-state.mjs +6 -2
  100. package/engine/note-vocabulary.mjs +74 -19
  101. package/engine/number-words.mjs +75 -0
  102. package/engine/office-holders.mjs +188 -0
  103. package/engine/pack-config.mjs +2 -15
  104. package/engine/pack-router.mjs +24 -5
  105. package/engine/packages.mjs +3 -2
  106. package/engine/pathnames.mjs +21 -24
  107. package/engine/pdf-build.mjs +217 -55
  108. package/engine/pdf-fonts.mjs +2 -2
  109. package/engine/pdf-images.mjs +82 -0
  110. package/engine/pdf-render.mjs +174 -22
  111. package/engine/populations.mjs +3 -2
  112. package/engine/project-init.mjs +499 -0
  113. package/engine/prose-config.mjs +1 -1
  114. package/engine/prose-lint.mjs +7 -6
  115. package/engine/readability-lint.mjs +86 -0
  116. package/engine/reckoning-markers.mjs +176 -166
  117. package/engine/scenes.mjs +5 -2
  118. package/engine/schema-check.mjs +3 -3
  119. package/engine/site-build.mjs +87 -37
  120. package/engine/site-config.mjs +8 -8
  121. package/engine/site-index.mjs +4 -0
  122. package/engine/site-maps.mjs +1 -1
  123. package/engine/site-preview.mjs +19 -8
  124. package/engine/social-ties.mjs +5 -1
  125. package/engine/sql-tables.mjs +92 -22
  126. package/engine/web-wikilinks.mjs +15 -31
  127. package/engine/wikilink-syntax.mjs +4 -1
  128. package/engine/wikilinks.mjs +12 -14
  129. package/hm3/item-fields.mjs +1 -1
  130. package/hm3/items.mjs +12 -45
  131. package/index.mjs +2 -14
  132. package/manifest.mjs +31 -4
  133. package/markdownlint-config.mjs +1 -1
  134. package/package.json +32 -11
  135. package/prettier-config.mjs +1 -1
  136. package/sohl/being-info.mjs +3 -145
  137. package/sohl/index.mjs +1 -1
  138. package/sohl/infobox.mjs +2 -1
  139. package/sohl/item-builders.mjs +3 -27
  140. package/sohl/item-fields.mjs +61 -22
  141. package/sohl/kb-passes.mjs +2 -2
  142. package/sohl/note-schemas.mjs +0 -8
  143. package/stage.mjs +19 -0
  144. package/types/config.d.mts +12 -3
  145. package/types/content-config.d.mts +15 -81
  146. package/types/engine/address-render.d.mts +13 -0
  147. package/types/engine/address.d.mts +2 -11
  148. package/types/engine/anchors.d.mts +3 -4
  149. package/types/engine/asset-bindings.d.mts +9 -2
  150. package/types/engine/base-compiler.d.mts +1 -2
  151. package/types/engine/being-measurements.d.mts +16 -0
  152. package/types/engine/calendar-choice.d.mts +14 -0
  153. package/types/engine/calendar-format.d.mts +33 -0
  154. package/types/engine/calendar-human.d.mts +4 -0
  155. package/types/engine/calendar-notes.d.mts +5 -3
  156. package/types/engine/calendars.d.mts +5 -3
  157. package/types/engine/code-fences.d.mts +6 -72
  158. package/types/engine/compile-corpus.d.mts +1 -3
  159. package/types/engine/content-admonitions.d.mts +24 -0
  160. package/types/engine/content-captions.d.mts +45 -0
  161. package/types/engine/content-footnotes.d.mts +10 -0
  162. package/types/engine/content-format-check.d.mts +17 -4
  163. package/types/engine/content-format.d.mts +24 -11
  164. package/types/engine/content-icons.d.mts +14 -23
  165. package/types/engine/content-images.d.mts +2 -2
  166. package/types/engine/content-index.d.mts +9 -2
  167. package/types/engine/content-links.d.mts +3 -5
  168. package/types/engine/content-tables.d.mts +11 -140
  169. package/types/engine/culture-choice.d.mts +14 -0
  170. package/types/engine/date-conversion.d.mts +4 -0
  171. package/types/engine/declaration-check.d.mts +24 -0
  172. package/types/engine/docs-checks.d.mts +28 -0
  173. package/types/engine/documentation-links.d.mts +7 -0
  174. package/types/engine/exported-scene.d.mts +6 -2
  175. package/types/engine/extension-attributes.d.mts +14 -0
  176. package/types/engine/field-spec.d.mts +9 -0
  177. package/types/engine/format-generated.d.mts +7 -0
  178. package/types/engine/frontmatter.d.mts +14 -4
  179. package/types/engine/generate.d.mts +1 -1
  180. package/types/engine/helpers.d.mts +37 -92
  181. package/types/engine/homepage.d.mts +2 -3
  182. package/types/engine/index.d.mts +5 -0
  183. package/types/engine/infobox-registry.d.mts +3 -1
  184. package/types/engine/infobox.d.mts +17 -6
  185. package/types/engine/item-compiler.d.mts +4 -4
  186. package/types/engine/item-docs.d.mts +4 -3
  187. package/types/engine/journals.d.mts +10 -7
  188. package/types/engine/map-graphviz.d.mts +1 -1
  189. package/types/engine/markdown-expressions.d.mts +17 -0
  190. package/types/engine/note-dates.d.mts +47 -1
  191. package/types/engine/note-package.d.mts +3 -19
  192. package/types/engine/note-state.d.mts +2 -1
  193. package/types/engine/note-vocabulary.d.mts +9 -0
  194. package/types/engine/number-words.d.mts +4 -0
  195. package/types/engine/office-holders.d.mts +14 -0
  196. package/types/engine/pack-config.d.mts +1 -1
  197. package/types/engine/packages.d.mts +3 -2
  198. package/types/engine/pdf-images.d.mts +38 -0
  199. package/types/engine/populations.d.mts +2 -2
  200. package/types/engine/project-init.d.mts +17 -0
  201. package/types/engine/prose-config.d.mts +1 -1
  202. package/types/engine/prose-lint.d.mts +3 -2
  203. package/types/engine/readability-lint.d.mts +22 -0
  204. package/types/engine/reckoning-markers.d.mts +13 -26
  205. package/types/engine/schema-check.d.mts +3 -3
  206. package/types/engine/site-build.d.mts +38 -28
  207. package/types/engine/site-config.d.mts +2 -2
  208. package/types/engine/site-index.d.mts +4 -0
  209. package/types/engine/site-preview.d.mts +1 -1
  210. package/types/engine/social-ties.d.mts +8 -2
  211. package/types/engine/sql-tables.d.mts +13 -2
  212. package/types/engine/web-wikilinks.d.mts +2 -1
  213. package/types/engine/wikilinks.d.mts +7 -1
  214. package/types/hm3/items.d.mts +5 -6
  215. package/types/labels.d.mts +40 -0
  216. package/types/manifest.d.mts +3 -3
  217. package/types/sohl/actors.d.mts +2 -2
  218. package/types/sohl/being-info.d.mts +3 -43
  219. package/types/sohl/index.d.mts +1 -1
  220. package/types/sohl/infobox.d.mts +5 -2
  221. package/types/sohl/item-fields.d.mts +4 -13
  222. package/types/stage.d.mts +9 -0
  223. package/CONTENT.md +0 -3239
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /**
15
- * The anchors a note declares on its headings — read in one place.
15
+ * The anchors a note declares on headings and captioned blocks.
16
16
  *
17
17
  * **A leaf, deliberately.** This is asked by the link checker, by the content
18
18
  * index, and by the builds that emit a link, and they cannot all import one
@@ -25,6 +25,8 @@
25
25
  * @module
26
26
  */
27
27
 
28
+ import { scanCaptions } from "./content-captions.mjs";
29
+
28
30
  /**
29
31
  * A heading, and the `{#slug}` anchor it declares.
30
32
  *
@@ -38,11 +40,10 @@
38
40
  const HEADING = /^\s*(#{1,6})\s+(.+?)\s*#*\s*$/;
39
41
  const ANCHOR = /^(.*?)\s*\{#([^}]+)\}\s*$/;
40
42
  /**
41
- * The `{#slug}` anchors a note's body declares, with where each one sits.
43
+ * The `{#slug}` and `:::caption {#slug}` anchors a note declares.
42
44
  *
43
- * Only headings carrying an explicit anchor are collected. A bare `#` heading
44
- * also starts a journal page, but it declares no slug, so nothing can address
45
- * it with `#…` — listing it would offer a link that cannot be written.
45
+ * A bare `#` heading starts a journal page without declaring a slug. Captioned
46
+ * blocks declare a slug and start an addressable journal page.
46
47
  *
47
48
  * @param {string} body - The note's markdown body, frontmatter already removed.
48
49
  * @param {number} [bodyLine] - The 1-based file line the body starts on, from
@@ -79,5 +80,13 @@ export function collectAnchors(body, bodyLine = 1) {
79
80
  line: bodyLine + i,
80
81
  });
81
82
  }
82
- return anchors;
83
+ for (const caption of scanCaptions(body).captions) {
84
+ anchors.push({
85
+ slug: caption.id,
86
+ name: caption.label,
87
+ level: 1,
88
+ line: bodyLine + caption.line - 1,
89
+ });
90
+ }
91
+ return anchors.sort((a, b) => a.line - b.line);
83
92
  }
@@ -22,7 +22,11 @@ function assetFiles(root) {
22
22
  return files;
23
23
  }
24
24
 
25
- /** Check configured foreign Address bindings against fetched indexes. */
25
+ /**
26
+ * Check configured foreign Address bindings against fetched indexes.
27
+ * @param {object} config
28
+ * @param {{foreignIndex?: Map<string, object>, assets?: object[]}} [options]
29
+ */
26
30
  export function checkForeignAssetBindings(config, { foreignIndex, assets } = {}) {
27
31
  const copies = (assets ?? config.packageBuild?.assets ?? []).filter((asset) => asset.bindsTo);
28
32
  if (!copies.length) return [];
@@ -39,7 +43,7 @@ export function checkForeignAssetBindings(config, { foreignIndex, assets } = {})
39
43
  {
40
44
  file: "package-build.config.yaml",
41
45
  severity: "error",
42
- message: `asset binding index for ${bindsTo.package} is unavailable; run content-build deps fetch`,
46
+ message: `asset binding index for ${bindsTo.package} is unavailable; run package-build deps fetch`,
43
47
  },
44
48
  ];
45
49
  }
@@ -63,6 +63,10 @@
63
63
 
64
64
  import { authoredFrontmatter } from "./index-records.mjs";
65
65
  import { renderSecretBlocks } from "./content-secrets.mjs";
66
+ import { scanAdmonitions } from "./content-admonitions.mjs";
67
+ import { scanCaptions } from "./content-captions.mjs";
68
+ import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
69
+ import { isGmNote } from "./note-vocabulary.mjs";
66
70
  import { cloneAddressState } from "./address-values.mjs";
67
71
  import fs from "fs";
68
72
  import path from "path";
@@ -82,7 +86,7 @@ import {
82
86
  import { isNoteRecord, noteFile } from "./index-records.mjs";
83
87
  import { reduceAddressFields } from "./address-fields.mjs";
84
88
  import { artPathname, artSlot, unacceptedArtMessage, unresolvedArtMessage } from "./art-fields.mjs";
85
- import { emitDiagnostic } from "./diagnostics.mjs";
89
+ import { emitDiagnostic, positionOfLiteral } from "./diagnostics.mjs";
86
90
  import { assertNoDeclaredPackage } from "./note-package.mjs";
87
91
  import { assertNoDeclaredFolder } from "./folder-notes.mjs";
88
92
  import {
@@ -434,6 +438,7 @@ export class BasePackCompiler {
434
438
  * routes to no pack at all — a build failure, never a silent drop.
435
439
  */
436
440
  routesHere(fm) {
441
+ if (isGmNote(fm) && !this.router?.privateOf?.(this.packName)) return false;
437
442
  if (!this.router || !this.packName || !this.docType) return true;
438
443
  return (
439
444
  this.router.resolve(fm, this.docType, this.packSystem ?? undefined) === this.packName
@@ -600,7 +605,6 @@ export class BasePackCompiler {
600
605
  // that converts no prose still compiles a document that carries art.
601
606
  this.linkIndex = this.corpus.linkIndex;
602
607
  if (this.constructor.convertsWikilinks) {
603
- this.contentDocs = this.corpus.contentDocs;
604
608
  this.sqlTables = this.corpus.sqlTables;
605
609
  }
606
610
  // The package's declared present, read once from the same corpus — so
@@ -622,7 +626,8 @@ export class BasePackCompiler {
622
626
  * that does not convert.
623
627
  */
624
628
  convertBody(fm, body) {
625
- const secretError = renderSecretBlocks(body, "book").errors[0];
629
+ const secretError =
630
+ renderSecretBlocks(body, "book").errors[0] ?? scanAdmonitions(body).errors[0];
626
631
  if (secretError) {
627
632
  const error = new Error(secretError.message);
628
633
  error.position = {
@@ -638,13 +643,35 @@ export class BasePackCompiler {
638
643
  const name = resolveName(fm);
639
644
  const { absPath, bodyLine, bodyColumn } = this.currentNote ?? {};
640
645
  const { markdown: tabulated, lineMap } = expandNoteTables(body, {
641
- docs: this.contentDocs,
642
646
  name,
643
- fm,
644
647
  bodyLine,
645
648
  sqlTables: absPath ? this.sqlTables?.get(absPath) : undefined,
646
649
  });
647
- const { markdown, unresolved } = convertNoteWikilinks(tabulated, {
650
+ const captionError = scanCaptions(tabulated).errors[0];
651
+ if (captionError) {
652
+ const error = new Error(captionError.message);
653
+ error.position = {
654
+ line:
655
+ (bodyLine ?? 1) +
656
+ (lineMap[captionError.line - 1]?.line ?? captionError.line - 1),
657
+ column: captionError.column,
658
+ };
659
+ throw error;
660
+ }
661
+ const expressions = renderMarkdownExpressions(tabulated, {
662
+ fm,
663
+ dates: reckoningContext(this.linkIndex),
664
+ sqlResults: absPath ? this.sqlTables?.inline?.get(absPath) : undefined,
665
+ file: absPath,
666
+ bodyLine,
667
+ });
668
+ if (expressions.findings.length) {
669
+ const finding = expressions.findings[0];
670
+ const error = new Error(finding.message);
671
+ error.position = { line: finding.line, column: finding.column };
672
+ throw error;
673
+ }
674
+ const { markdown, unresolved } = convertNoteWikilinks(expressions.markdown, {
648
675
  type: fm.type,
649
676
  id: fm.id,
650
677
  // Where this note is, so a link that resolves nowhere is reported
@@ -808,7 +835,7 @@ export class BasePackCompiler {
808
835
  * a line rather than at a note.
809
836
  *
810
837
  * Silent where nothing can answer — no published schema, or a subtype the
811
- * artifact does not name. `content-build lint` says that out loud once for
838
+ * artifact does not name. `package-build lint` says that out loud once for
812
839
  * the whole build rather than once per note.
813
840
  *
814
841
  * @param {object} fm - The note's frontmatter.
@@ -1211,13 +1238,22 @@ export class BasePackCompiler {
1211
1238
  this.onCompiled(fm, doc);
1212
1239
  } catch (err) {
1213
1240
  this.errorCount++;
1241
+ const repeatedAnchor = err.message.match(
1242
+ /declares the anchor \{#([^}]+)\} on more than one heading/,
1243
+ );
1244
+ const position =
1245
+ err.position ??
1246
+ (repeatedAnchor ?
1247
+ positionOfLiteral(
1248
+ fs.readFileSync(absPath, "utf8"),
1249
+ `{#${repeatedAnchor[1]}}`,
1250
+ 2,
1251
+ )
1252
+ : undefined);
1214
1253
  // `position` is set by whatever failed if it knew where — an
1215
1254
  // unresolved address, a bad table directive — so the report
1216
1255
  // points at the line rather than at the note.
1217
- this.noteError(
1218
- `${this.noteLabel(fm)} failed to compile: ${err.message}`,
1219
- err.position,
1220
- );
1256
+ this.noteError(`${this.noteLabel(fm)} failed to compile: ${err.message}`, position);
1221
1257
  }
1222
1258
  }
1223
1259
 
@@ -0,0 +1,70 @@
1
+ /* SPDX-License-Identifier: GPL-3.0-or-later */
2
+
3
+ import { positionOfFrontmatterPath } from "./diagnostics.mjs";
4
+
5
+ const INCHES_PER_METRE = 39.3701;
6
+ const POUNDS_PER_KILOGRAM = 2.20462;
7
+
8
+ /** Measurements are displayed as whole imperial units. */
9
+ function safeRound(value) {
10
+ const rounded = Math.round(value);
11
+ return Number.isSafeInteger(rounded) && rounded >= 0 ? rounded : null;
12
+ }
13
+
14
+ /** Read a being's height as a whole number of inches. */
15
+ export function parseBeingHeight(value) {
16
+ if (typeof value === "number")
17
+ return Number.isFinite(value) && value >= 0 ? safeRound(value * INCHES_PER_METRE) : null;
18
+ if (typeof value !== "string") return null;
19
+ const metric = /^(\d+(?:\.\d+)?) ?m$/.exec(value);
20
+ if (metric) return safeRound(Number(metric[1]) * INCHES_PER_METRE);
21
+ const imperial = /^(\d+)'(?:\s*(\d{1,2})")?$/.exec(value);
22
+ if (!imperial) return null;
23
+ const inches = Number(imperial[2] ?? 0);
24
+ return inches <= 11 ? safeRound(Number(imperial[1]) * 12 + inches) : null;
25
+ }
26
+
27
+ /** Read a being's weight as a whole number of pounds. */
28
+ export function parseBeingWeight(value) {
29
+ if (typeof value === "number")
30
+ return Number.isFinite(value) && value >= 0 ? safeRound(value * POUNDS_PER_KILOGRAM) : null;
31
+ if (typeof value !== "string") return null;
32
+ const metric = /^(\d+(?:\.\d+)?) ?kg$/.exec(value);
33
+ if (metric) return safeRound(Number(metric[1]) * POUNDS_PER_KILOGRAM);
34
+ const imperial = /^(\d+) ?lbs$/.exec(value);
35
+ return imperial ? safeRound(Number(imperial[1])) : null;
36
+ }
37
+
38
+ /** Show a being's height in feet and inches. */
39
+ export function displayBeingHeight(value) {
40
+ const inches = parseBeingHeight(value);
41
+ if (inches === null) return String(value);
42
+ const feet = Math.floor(inches / 12);
43
+ const remainder = inches % 12;
44
+ return String(feet) + "′" + (remainder ? " " + String(remainder) + "″" : "");
45
+ }
46
+
47
+ /** Show a being's weight in pounds. */
48
+ export function displayBeingWeight(value) {
49
+ const pounds = parseBeingWeight(value);
50
+ return pounds === null ? String(value) : String(pounds) + " lbs";
51
+ }
52
+
53
+ /** Check one authored being measurement at its frontmatter position. */
54
+ export function checkBeingMeasurement(note, key, parse) {
55
+ const value = note.fm?.data?.[key];
56
+ if (value === undefined || value === null || parse(value) !== null) return [];
57
+ return [
58
+ {
59
+ ...(note.file ? { file: note.file } : {}),
60
+ ...positionOfFrontmatterPath(note.raw ?? "", ["data", key]),
61
+ severity: "error",
62
+ message:
63
+ "data." +
64
+ key +
65
+ (key === "height" ?
66
+ " needs metres or feet and inches, such as 1.91m or 6' 3\""
67
+ : " needs kilograms or pounds, such as 85kg or 187 lbs"),
68
+ },
69
+ ];
70
+ }
@@ -32,7 +32,7 @@ function readIndex(file) {
32
32
  const display = path.relative(process.cwd(), file) || file;
33
33
  if (!fs.existsSync(file)) {
34
34
  const error = new Error(
35
- `${display}: error: content index is absent; run content-build content-index`,
35
+ `${display}: error: content index is absent; run package-build content-index`,
36
36
  );
37
37
  error.located = true;
38
38
  throw error;
@@ -0,0 +1,32 @@
1
+ /* SPDX-License-Identifier: GPL-3.0-or-later */
2
+
3
+ import { isAddressTuple, parseAddress, renderAddress } from "./address.mjs";
4
+ import { positionOfFrontmatterPath } from "./diagnostics.mjs";
5
+
6
+ /**
7
+ * Require a being's or place's chosen calendar to address a calendar note.
8
+ * @param {object} note
9
+ * @param {{index?: object}} [options]
10
+ */
11
+ export function checkCalendarChoice(note, { index } = {}) {
12
+ const value = note.fm?.data?.calendar;
13
+ if (value === undefined || value === null || !index?.addressHit) return [];
14
+ const tuple = parseAddress(value, {
15
+ package: index.contentPackage,
16
+ system: "note",
17
+ type: "lore",
18
+ types: index.types,
19
+ packages: index.packages,
20
+ });
21
+ if (tuple.reason || tuple.type !== "lore") return [];
22
+ const target = index.addressHit(renderAddress(tuple));
23
+ if (!target || (target.fm ?? target).subType === "calendar") return [];
24
+ return [
25
+ {
26
+ file: note.file,
27
+ ...positionOfFrontmatterPath(note.raw ?? "", ["data", "calendar"]),
28
+ severity: "error",
29
+ message: `data.calendar ${isAddressTuple(value) ? renderAddress(value) : value} names lore whose subType is not calendar`,
30
+ },
31
+ ];
32
+ }