@heroiclands/package-build 22.15.1 → 22.16.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 (221) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +39 -678
  3. package/bin/{content-build.mjs → content-commands.mjs} +197 -118
  4. package/bin/package-build.mjs +303 -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 +294 -234
  12. package/docs/authoring/assets.md +31 -0
  13. package/docs/authoring/dates-and-calendars.md +33 -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 +631 -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 +4420 -0
  36. package/docs/reference/note-types.md +531 -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-human.mjs +71 -0
  46. package/engine/calendar-notes.mjs +41 -3
  47. package/engine/calendars.mjs +8 -1
  48. package/engine/changelog-lint.mjs +2 -2
  49. package/engine/code-fences.mjs +23 -97
  50. package/engine/compile-corpus.mjs +6 -7
  51. package/engine/content-admonitions.mjs +114 -0
  52. package/engine/content-captions.mjs +181 -0
  53. package/engine/content-embeds.mjs +2 -2
  54. package/engine/content-footnotes.mjs +34 -0
  55. package/engine/content-format-check.mjs +2 -2
  56. package/engine/content-format.mjs +127 -73
  57. package/engine/content-format.yaml +745 -0
  58. package/engine/content-html.mjs +1 -1
  59. package/engine/content-icons.mjs +45 -92
  60. package/engine/content-images.mjs +22 -74
  61. package/engine/content-index.mjs +12 -3
  62. package/engine/content-links.mjs +21 -26
  63. package/engine/content-lint.mjs +1 -5
  64. package/engine/content-secrets.mjs +3 -1
  65. package/engine/content-tables.mjs +47 -1257
  66. package/engine/culture-choice.mjs +32 -0
  67. package/engine/date-conversion.mjs +47 -0
  68. package/engine/declaration-check.mjs +87 -0
  69. package/engine/docs-checks.mjs +140 -0
  70. package/engine/documentation-links.mjs +31 -0
  71. package/engine/exported-scene.mjs +1 -1
  72. package/engine/extension-attributes.mjs +74 -0
  73. package/engine/field-reference.mjs +1 -1
  74. package/engine/field-spec.mjs +4 -0
  75. package/engine/foreign-catalog.mjs +5 -5
  76. package/engine/format-generated.mjs +15 -0
  77. package/engine/foundry-entries.mjs +3 -1
  78. package/engine/frontmatter-lint.mjs +5 -1
  79. package/engine/frontmatter.mjs +10 -0
  80. package/engine/generate.mjs +2 -2
  81. package/engine/helpers.mjs +71 -117
  82. package/engine/homepage.mjs +2 -3
  83. package/engine/index-records.mjs +1 -0
  84. package/engine/index.mjs +18 -3
  85. package/engine/infobox-registry.mjs +3 -1
  86. package/engine/infobox.mjs +38 -20
  87. package/engine/item-compiler.mjs +10 -8
  88. package/engine/item-docs.mjs +7 -6
  89. package/engine/journals.mjs +51 -10
  90. package/engine/macros.mjs +1 -1
  91. package/engine/map-build.mjs +1 -1
  92. package/engine/map-dot.mjs +1 -1
  93. package/engine/markdown-expressions.mjs +117 -0
  94. package/engine/metadata-index.mjs +7 -3
  95. package/engine/note-addresses.mjs +1 -0
  96. package/engine/note-dates.mjs +283 -61
  97. package/engine/note-package.mjs +5 -25
  98. package/engine/note-state.mjs +6 -2
  99. package/engine/note-vocabulary.mjs +74 -19
  100. package/engine/number-words.mjs +75 -0
  101. package/engine/office-holders.mjs +188 -0
  102. package/engine/pack-config.mjs +2 -15
  103. package/engine/pack-router.mjs +24 -5
  104. package/engine/packages.mjs +3 -2
  105. package/engine/pathnames.mjs +21 -24
  106. package/engine/pdf-build.mjs +217 -55
  107. package/engine/pdf-fonts.mjs +2 -2
  108. package/engine/pdf-images.mjs +82 -0
  109. package/engine/pdf-render.mjs +174 -22
  110. package/engine/populations.mjs +3 -2
  111. package/engine/project-init.mjs +499 -0
  112. package/engine/prose-config.mjs +1 -1
  113. package/engine/prose-lint.mjs +7 -6
  114. package/engine/readability-lint.mjs +86 -0
  115. package/engine/reckoning-markers.mjs +205 -93
  116. package/engine/scenes.mjs +5 -2
  117. package/engine/schema-check.mjs +3 -3
  118. package/engine/site-build.mjs +87 -37
  119. package/engine/site-config.mjs +8 -8
  120. package/engine/site-index.mjs +4 -0
  121. package/engine/site-maps.mjs +1 -1
  122. package/engine/site-preview.mjs +19 -8
  123. package/engine/social-ties.mjs +5 -1
  124. package/engine/sql-tables.mjs +92 -22
  125. package/engine/web-wikilinks.mjs +15 -31
  126. package/engine/wikilink-syntax.mjs +4 -1
  127. package/engine/wikilinks.mjs +12 -14
  128. package/hm3/item-fields.mjs +1 -1
  129. package/hm3/items.mjs +12 -45
  130. package/index.mjs +2 -14
  131. package/manifest.mjs +31 -4
  132. package/markdownlint-config.mjs +1 -1
  133. package/package.json +32 -11
  134. package/prettier-config.mjs +1 -1
  135. package/sohl/being-info.mjs +3 -145
  136. package/sohl/index.mjs +1 -1
  137. package/sohl/infobox.mjs +2 -1
  138. package/sohl/item-builders.mjs +3 -27
  139. package/sohl/item-fields.mjs +61 -22
  140. package/sohl/kb-passes.mjs +2 -2
  141. package/sohl/note-schemas.mjs +0 -8
  142. package/stage.mjs +19 -0
  143. package/types/config.d.mts +12 -3
  144. package/types/content-config.d.mts +15 -81
  145. package/types/engine/address-render.d.mts +13 -0
  146. package/types/engine/address.d.mts +2 -11
  147. package/types/engine/anchors.d.mts +3 -4
  148. package/types/engine/asset-bindings.d.mts +9 -2
  149. package/types/engine/base-compiler.d.mts +1 -2
  150. package/types/engine/being-measurements.d.mts +16 -0
  151. package/types/engine/calendar-choice.d.mts +14 -0
  152. package/types/engine/calendar-human.d.mts +4 -0
  153. package/types/engine/calendar-notes.d.mts +2 -0
  154. package/types/engine/calendars.d.mts +5 -3
  155. package/types/engine/code-fences.d.mts +6 -72
  156. package/types/engine/compile-corpus.d.mts +1 -3
  157. package/types/engine/content-admonitions.d.mts +24 -0
  158. package/types/engine/content-captions.d.mts +45 -0
  159. package/types/engine/content-footnotes.d.mts +10 -0
  160. package/types/engine/content-format-check.d.mts +17 -4
  161. package/types/engine/content-format.d.mts +24 -11
  162. package/types/engine/content-icons.d.mts +14 -23
  163. package/types/engine/content-images.d.mts +2 -2
  164. package/types/engine/content-index.d.mts +9 -2
  165. package/types/engine/content-links.d.mts +3 -5
  166. package/types/engine/content-tables.d.mts +11 -140
  167. package/types/engine/culture-choice.d.mts +14 -0
  168. package/types/engine/date-conversion.d.mts +4 -0
  169. package/types/engine/declaration-check.d.mts +24 -0
  170. package/types/engine/docs-checks.d.mts +28 -0
  171. package/types/engine/documentation-links.d.mts +7 -0
  172. package/types/engine/exported-scene.d.mts +6 -2
  173. package/types/engine/extension-attributes.d.mts +14 -0
  174. package/types/engine/field-spec.d.mts +9 -0
  175. package/types/engine/format-generated.d.mts +7 -0
  176. package/types/engine/frontmatter.d.mts +14 -4
  177. package/types/engine/generate.d.mts +1 -1
  178. package/types/engine/helpers.d.mts +37 -92
  179. package/types/engine/homepage.d.mts +2 -3
  180. package/types/engine/index.d.mts +5 -0
  181. package/types/engine/infobox-registry.d.mts +3 -1
  182. package/types/engine/infobox.d.mts +17 -6
  183. package/types/engine/item-compiler.d.mts +4 -4
  184. package/types/engine/item-docs.d.mts +4 -3
  185. package/types/engine/journals.d.mts +10 -7
  186. package/types/engine/map-graphviz.d.mts +1 -1
  187. package/types/engine/markdown-expressions.d.mts +17 -0
  188. package/types/engine/note-dates.d.mts +47 -1
  189. package/types/engine/note-package.d.mts +3 -19
  190. package/types/engine/note-state.d.mts +2 -1
  191. package/types/engine/note-vocabulary.d.mts +9 -0
  192. package/types/engine/number-words.d.mts +4 -0
  193. package/types/engine/office-holders.d.mts +14 -0
  194. package/types/engine/pack-config.d.mts +1 -1
  195. package/types/engine/packages.d.mts +3 -2
  196. package/types/engine/pdf-images.d.mts +38 -0
  197. package/types/engine/populations.d.mts +2 -2
  198. package/types/engine/project-init.d.mts +17 -0
  199. package/types/engine/prose-config.d.mts +1 -1
  200. package/types/engine/prose-lint.d.mts +3 -2
  201. package/types/engine/readability-lint.d.mts +22 -0
  202. package/types/engine/reckoning-markers.d.mts +4 -2
  203. package/types/engine/schema-check.d.mts +3 -3
  204. package/types/engine/site-build.d.mts +38 -28
  205. package/types/engine/site-config.d.mts +2 -2
  206. package/types/engine/site-index.d.mts +4 -0
  207. package/types/engine/site-preview.d.mts +1 -1
  208. package/types/engine/social-ties.d.mts +8 -2
  209. package/types/engine/sql-tables.d.mts +13 -2
  210. package/types/engine/web-wikilinks.d.mts +2 -1
  211. package/types/engine/wikilinks.d.mts +7 -1
  212. package/types/hm3/items.d.mts +5 -6
  213. package/types/labels.d.mts +40 -0
  214. package/types/manifest.d.mts +3 -3
  215. package/types/sohl/actors.d.mts +2 -2
  216. package/types/sohl/being-info.d.mts +3 -43
  217. package/types/sohl/index.d.mts +1 -1
  218. package/types/sohl/infobox.d.mts +5 -2
  219. package/types/sohl/item-fields.d.mts +4 -13
  220. package/types/stage.d.mts +9 -0
  221. package/CONTENT.md +0 -3239
@@ -14,7 +14,7 @@
14
14
  /**
15
15
  * Publishing a content tree as a website.
16
16
  *
17
- * Compiling a content tree into compendium packs is `content-build package
17
+ * Compiling a content tree into compendium packs is `package-build package
18
18
  * compile`. Publishing the *same tree* as a website was a script each consumer
19
19
  * wrote for itself — 473 code lines in `sohl` and 462 in `sohl-thalorna`, 87 of
20
20
  * them identical — and the copies drifted in ways neither repository could see.
@@ -60,8 +60,11 @@ import matter from "gray-matter";
60
60
 
61
61
  import { addressSlug } from "./content-address.mjs";
62
62
  import { protectCode } from "./code-fences.mjs";
63
+ import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
63
64
  import { expandContentTables } from "./content-tables.mjs";
64
65
  import { renderSecretBlocks } from "./content-secrets.mjs";
66
+ import { renderAdmonitions, scanAdmonitions } from "./content-admonitions.mjs";
67
+ import { renderCaptionBlocks, scanCaptions } from "./content-captions.mjs";
65
68
  import { renderImageFigures } from "./content-images.mjs";
66
69
  import { pathnameProblem, resolvePathname } from "./pathnames.mjs";
67
70
  import { buildSiteIndex, resolveInfoboxRef, wikiContext } from "./site-index.mjs";
@@ -69,7 +72,6 @@ import { frontmatterWikilinks, resolveWebWikilinks } from "./web-wikilinks.mjs";
69
72
  import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
70
73
  import { noteInfoboxes } from "./infobox-registry.mjs";
71
74
  import { formatUnaddressableFinding, unaddressableForeignPackages } from "./metadata-index.mjs";
72
- import { deriveBeingInfo, isBeing } from "../sohl/being-info.mjs";
73
75
  import { loadPackConfig } from "./pack-config.mjs";
74
76
  import { routerFor } from "./pack-router.mjs";
75
77
  import { searchableFrontmatter } from "./note-package.mjs";
@@ -79,9 +81,11 @@ import { indexRecordsFor } from "./content-index.mjs";
79
81
  // reuses `collectContentPages`'s pages, so wiring it here reaches the book too.
80
82
  import { applyComputedBeingAge, presentAmongRecords } from "./being-age.mjs";
81
83
  import { reckoningContext } from "./reckoning-markers.mjs";
84
+ import { resolvedDateFields } from "./note-dates.mjs";
82
85
  import { isNoteRecord, noteFile } from "./index-records.mjs";
83
86
  // The one statement of what an empty body means, shared with the index.
84
87
  import { isStubNote } from "./note-state.mjs";
88
+ import { isGmNote } from "./note-vocabulary.mjs";
85
89
  import { ART_SLOTS, artPathname, assetAddressIndex } from "./art-fields.mjs";
86
90
  import {
87
91
  HOMEPAGE_DESTINATION,
@@ -226,7 +230,7 @@ export function collectContentPages(contentBase, ctx) {
226
230
  // the index apply one rule from one module and a note that cannot be
227
231
  // addressed for some *other* reason still reaches the finding below
228
232
  // that says so.
229
- if (isStubNote(fm, body)) continue;
233
+ if (isStubNote(fm, body) || isGmNote(fm)) continue;
230
234
 
231
235
  for (const hit of frontmatterWikilinks(fm)) {
232
236
  fmLinkFindings.push({ file, ...hit });
@@ -286,11 +290,8 @@ export function collectContentPages(contentBase, ctx) {
286
290
  /**
287
291
  * The package's homepage notes — the authored page at `/<contentPackage>/`.
288
292
  *
289
- * A separate walk from {@link collectContentPages} rather than a branch inside
290
- * it, because in homepage-only mode it is the **whole** of the site build: the
291
- * content tree is never read for pages at all, so the licensing constraint two
292
- * packages ship under is a property of the code path rather than of a
293
- * configuration that happens to be empty.
293
+ * A separate collection from {@link collectContentPages} keeps the homepage
294
+ * at the package root and leaves content pages at their note addresses.
294
295
  *
295
296
  * Returned as a list rather than as the one note there should be, because the
296
297
  * count is what {@link checkHomepageCount} judges — this walk reports
@@ -298,8 +299,7 @@ export function collectContentPages(contentBase, ctx) {
298
299
  *
299
300
  * A homepage that declares no `shortcode` has no address, and is
300
301
  * reported rather than written: it is the same finding a content page's missing
301
- * shortcode produces, and it has to be available in homepage-only mode, where
302
- * no other gate runs.
302
+ * shortcode produces, and it is reported even when the tree has no other notes.
303
303
  *
304
304
  * **It is still counted.** An unaddressable homepage is a homepage — dropping
305
305
  * it from the list would make {@link checkHomepageCount} report a tree with one
@@ -317,6 +317,10 @@ export function collectHomepages(contentBase, ctx) {
317
317
  for (const file of siteCorpusFiles(contentBase, ctx)) {
318
318
  const note = readNote(file, ctx);
319
319
  if (!note || !isHomepage(note.fm)) continue;
320
+ if (isGmNote(note.fm)) {
321
+ addressFindings.push({ file, reason: "a GM-tagged homepage cannot publish publicly" });
322
+ continue;
323
+ }
320
324
  try {
321
325
  addressSlug(note.fm);
322
326
  } catch (err) {
@@ -332,13 +336,10 @@ export function collectHomepages(contentBase, ctx) {
332
336
  *
333
337
  * Its own writer, deliberately small. A homepage is authored markdown published
334
338
  * verbatim — no table expansion and no link resolution — so routing it through
335
- * {@link renderPages} would buy it a pipeline it has no input for, and would
336
- * make homepage-only mode depend on the index, the foreign manifests and the
337
- * table universe that mode exists to not build.
339
+ * {@link renderPages} would give it a pipeline it does not use.
338
340
  *
339
341
  * **Verbatim is the answer, not a gap.** A homepage's links could not be
340
- * *resolved* here without giving `homepage` mode the index its licensing fence
341
- * exists to not build, so they are **checked** instead:
342
+ * *resolved* here, so they are **checked** instead:
342
343
  * {@link auditHomepageLinks} reads the body's markdown links, and reports a
343
344
  * wikilink on the page rather than resolving one.
344
345
  *
@@ -351,8 +352,8 @@ export function collectHomepages(contentBase, ctx) {
351
352
  * both sides of the link graph: a content page reaches it through
352
353
  * `[[homepage-root|Text]]`, and its own markdown links name content pages. So
353
354
  * it carries the same `related` block every content page does — handed in by
354
- * the caller, since the graph is read off the content render and homepage-only
355
- * mode has none.
355
+ * the caller, since the graph is read off the content render and a tree with
356
+ * only a homepage has no such graph.
356
357
  *
357
358
  * @param {string} outRoot - The package's site root — the content mount's
358
359
  * root, `build/hugo/content`, one level above the mount itself.
@@ -362,7 +363,7 @@ export function collectHomepages(contentBase, ctx) {
362
363
  * @param {object} [options] - Options.
363
364
  * @param {import("./related-pages.mjs").Related} [options.related] - The
364
365
  * homepage's backlinks and mentions. Absent where nothing connects to it,
365
- * and in homepage-only mode, where no link resolves.
366
+ * and when the tree contains only a homepage, where no content link resolves.
366
367
  * @returns {number} How many pages were written.
367
368
  */
368
369
  export function writeHomepages(outRoot, pages, config, { related } = {}) {
@@ -402,6 +403,7 @@ export function writeHomepages(outRoot, pages, config, { related } = {}) {
402
403
  * collection.
403
404
  * @param {object} options
404
405
  * @param {object} options.config - The resolved build configuration.
406
+ * @param {object[]} options.records - Content-index records.
405
407
  * @returns {object} The gate results and, when they pass, the built index.
406
408
  */
407
409
  export function siteGates(pages, findings, { config, records }) {
@@ -469,7 +471,13 @@ export function emptyGates() {
469
471
  };
470
472
  }
471
473
 
472
- /** Whether any gate produced a finding. */
474
+ /**
475
+ * Whether any gate produced a finding.
476
+ * @param {{homepages: unknown[], frontmatterLinks: unknown[],
477
+ * addressErrors: unknown[], staleManifests: unknown[],
478
+ * unaddressable: unknown[]}} gates - Results of the site checks.
479
+ * @returns {boolean} Whether at least one check found an error.
480
+ */
473
481
  export function gatesFailed(gates) {
474
482
  return Boolean(
475
483
  gates.homepages.length ||
@@ -668,7 +676,7 @@ function isPlainObject(value) {
668
676
  * `_index.md`, and the page's stated `url` keeps its address exactly where the
669
677
  * flat file's was.
670
678
  *
671
- * @param {object} page - The page.
679
+ * @param {{slug: string}} page - The page.
672
680
  * @param {object} [opts]
673
681
  * @param {boolean} [opts.bundle=false] - Whether the page carries a resource.
674
682
  * @returns {string} The file, relative to the mount.
@@ -693,7 +701,9 @@ export function renderSitePage(
693
701
  },
694
702
  ) {
695
703
  const tableErrors = [];
704
+ const expressionErrors = [];
696
705
  const secretErrors = [];
706
+ const captionErrors = [];
697
707
  const wikiErrors = [];
698
708
  const imageErrors = [];
699
709
  const resolved = [];
@@ -738,7 +748,7 @@ export function renderSitePage(
738
748
  return renderImageFigures(transformed, webSrc);
739
749
  };
740
750
 
741
- const { markdown, errors } = expandContentTables(page.body, {
751
+ const { markdown, errors, lineMap } = expandContentTables(page.body, {
742
752
  docs: universe.get(page.pkg) ?? [],
743
753
  linkable,
744
754
  source: src,
@@ -746,8 +756,27 @@ export function renderSitePage(
746
756
  self: { fm: searchableFrontmatter(page.fm, page.pkg), path: page.relPath },
747
757
  });
748
758
  tableErrors.push(...errors);
759
+ const captionScan = scanCaptions(markdown);
760
+ ctx.captionLabels = new Map(captionScan.captions.map((caption) => [caption.id, caption.label]));
761
+ const expressions = renderMarkdownExpressions(markdown, {
762
+ fm: page.fm,
763
+ dates: index.dateContext,
764
+ file: page.file,
765
+ bodyLine: page.bodyLine,
766
+ sqlResults: sqlTables?.inline?.get(page.file),
767
+ });
768
+ expressionErrors.push(...expressions.findings);
749
769
  const data = pageFrontmatter(page, { decorate, webSrc, artSrc });
750
- const secrets = renderSecretBlocks(protectCode(markdown, resolve), "web");
770
+ const secrets = renderSecretBlocks(protectCode(expressions.markdown, resolve), "web");
771
+ const admonitions = renderAdmonitions(secrets.markdown);
772
+ const captioned = renderCaptionBlocks(admonitions.markdown);
773
+ for (const error of captionScan.errors)
774
+ captionErrors.push({
775
+ file: page.file,
776
+ line: (page.bodyLine ?? 1) + (lineMap[error.line - 1]?.line ?? error.line - 1),
777
+ column: error.column,
778
+ message: error.message,
779
+ });
751
780
  for (const error of renderSecretBlocks(page.body, "book").errors)
752
781
  secretErrors.push({
753
782
  file: page.file,
@@ -755,13 +784,22 @@ export function renderSitePage(
755
784
  column: error.column,
756
785
  message: error.message,
757
786
  });
787
+ for (const error of scanAdmonitions(page.body).errors)
788
+ secretErrors.push({
789
+ file: page.file,
790
+ line: (page.bodyLine ?? 1) + error.line - 1,
791
+ column: error.column,
792
+ message: error.message,
793
+ });
758
794
  return {
759
795
  page,
760
- body: secrets.markdown,
796
+ body: captioned.markdown,
761
797
  data,
762
798
  resolved,
763
799
  tableErrors,
800
+ expressionErrors,
764
801
  secretErrors,
802
+ captionErrors,
765
803
  wikiErrors,
766
804
  imageErrors,
767
805
  };
@@ -774,7 +812,7 @@ export function renderSitePage(
774
812
  * compilers use:
775
813
  *
776
814
  * 1. **Tables expand first**, and outside code-fence protection. A table is
777
- * authored as a fenced `dataview` block, which `protectCode` would otherwise
815
+ * authored as a fenced `sql` block, which `protectCode` would otherwise
778
816
  * stash away before the expander saw it. Expanding first leaves an ordinary
779
817
  * markdown table to walk, with every other fence still protected.
780
818
  * 2. **Then, inside protection**: the consumer's `beforeLinks` pass, then
@@ -836,7 +874,9 @@ export function renderPages(pages, options) {
836
874
  });
837
875
 
838
876
  const tableErrors = [];
877
+ const expressionErrors = [];
839
878
  const secretErrors = [];
879
+ const captionErrors = [];
840
880
  const wikiErrors = [];
841
881
  const imageErrors = [];
842
882
  const byKind = {};
@@ -877,7 +917,9 @@ export function renderPages(pages, options) {
877
917
  artIndex,
878
918
  });
879
919
  tableErrors.push(...result.tableErrors);
920
+ expressionErrors.push(...result.expressionErrors);
880
921
  secretErrors.push(...result.secretErrors);
922
+ captionErrors.push(...result.captionErrors);
881
923
  wikiErrors.push(...result.wikiErrors);
882
924
  imageErrors.push(...result.imageErrors);
883
925
  rendered.push({ page, body: result.body, data: result.data });
@@ -918,7 +960,9 @@ export function renderPages(pages, options) {
918
960
  written: pages.length,
919
961
  byKind,
920
962
  tableErrors,
963
+ expressionErrors,
921
964
  secretErrors,
965
+ captionErrors,
922
966
  wikiErrors,
923
967
  imageErrors,
924
968
  related,
@@ -940,6 +984,7 @@ export function renderPages(pages, options) {
940
984
  */
941
985
  const SITE_PASSES = Object.freeze({
942
986
  sohlKb: () => require("../sohl/kb-passes.mjs").sohlKbPass,
987
+ packageDocs: () => require("./documentation-links.mjs").documentationLinksPass,
943
988
  });
944
989
 
945
990
  /**
@@ -965,10 +1010,12 @@ export function resolveSitePass(name, options) {
965
1010
  export function sitePageDecorator(config, index) {
966
1011
  const router = routerFor(config);
967
1012
  return (data, page) => {
968
- if (isBeing(page.fm)) data.sohl = deriveBeingInfo(page.fm.sohl, index.refIndex);
1013
+ const resolvedDates = resolvedDateFields(page.fm, index.dateContext);
1014
+ if (Object.keys(resolvedDates).length) data.resolvedDates = resolvedDates;
969
1015
  data.infoboxes = noteInfoboxes(page.fm, {
970
1016
  resolve: (ref, hint) => resolveInfoboxRef(index, ref, hint),
971
1017
  router,
1018
+ dates: index.dateContext,
972
1019
  });
973
1020
  };
974
1021
  }
@@ -998,9 +1045,7 @@ export function buildSite({ config, sqlTables } = {}) {
998
1045
  const resolved = config ?? loadPackConfig();
999
1046
  const site = resolved.site;
1000
1047
  const scheme = resolved.publish.address;
1001
- // Homepage-only or homepage-plus-content. The floor is the homepage,
1002
- // so this decides whether the *content* surfaces are published, never
1003
- // whether anything is.
1048
+ // A tree containing only the homepage has no content pages to publish.
1004
1049
  const publishesContent = publishesContentPages(resolved);
1005
1050
 
1006
1051
  // Where the package is served, and where its content mounts inside it. The
@@ -1023,12 +1068,12 @@ export function buildSite({ config, sqlTables } = {}) {
1023
1068
  const out =
1024
1069
  publishesContent ?
1025
1070
  path.join(outBase, scheme.prefix.replace(/\/$/, ""))
1026
- // Homepage-only has no content mount, so the package's root *is*
1071
+ // A tree containing only the homepage has no content mount, so its root is
1027
1072
  // the output root.
1028
1073
  : outBase;
1029
1074
  // The homepage publishes at `/<contentPackage>/`, so its file goes at the
1030
1075
  // package's own root — one level above the content mount, and the same
1031
- // directory in homepage-only mode.
1076
+ // directory when the tree contains only a homepage.
1032
1077
  const homeRoot = publishesContent ? outBase : out;
1033
1078
 
1034
1079
  const packages = new Set(site.packages.length ? site.packages : [resolved.contentPackage]);
@@ -1059,12 +1104,8 @@ export function buildSite({ config, sqlTables } = {}) {
1059
1104
  const collected = collectHomepages(resolved.paths.content, ctx);
1060
1105
  const homepages = collected.pages;
1061
1106
 
1062
- // Exactly one homepage, and checked here — before the output tree is
1063
- // cleared and before either mode branches. Before the clear, because
1064
- // a gate that fired after it would have destroyed a good site to report a
1065
- // bad tree. Before the branch, because the requirement does not vary by
1066
- // mode: `publish.site` chooses whether the *content* surfaces are
1067
- // published, and the homepage is the floor beneath both.
1107
+ // Check for exactly one homepage before clearing the output tree, so a
1108
+ // malformed tree does not destroy the last good site.
1068
1109
  //
1069
1110
  // The count is judged first, and alone when it fires: a tree with two
1070
1111
  // homepages does not need to be told about each one's address as well, and
@@ -1074,7 +1115,7 @@ export function buildSite({ config, sqlTables } = {}) {
1074
1115
  contentPackage: resolved.contentPackage,
1075
1116
  });
1076
1117
  // A homepage that cannot be addressed is reported in the same place, and
1077
- // reaches homepage-only mode — which runs no other gate at all.
1118
+ // also applies when the tree contains only a homepage.
1078
1119
  const homepageFindings =
1079
1120
  counted.length ? counted : (
1080
1121
  collected.addressFindings.map((f) => ({
@@ -1088,7 +1129,9 @@ export function buildSite({ config, sqlTables } = {}) {
1088
1129
  gates: { ...emptyGates(), homepages: homepageFindings },
1089
1130
  manifests: null,
1090
1131
  tableErrors: [],
1132
+ expressionErrors: [],
1091
1133
  secretErrors: [],
1134
+ captionErrors: [],
1092
1135
  wikiErrors: [],
1093
1136
  imageErrors: [],
1094
1137
  mapFindings: [],
@@ -1108,7 +1151,9 @@ export function buildSite({ config, sqlTables } = {}) {
1108
1151
  gates: emptyGates(),
1109
1152
  manifests: null,
1110
1153
  tableErrors: [],
1154
+ expressionErrors: [],
1111
1155
  secretErrors: [],
1156
+ captionErrors: [],
1112
1157
  wikiErrors: [],
1113
1158
  imageErrors: [],
1114
1159
  mapFindings: [],
@@ -1154,7 +1199,9 @@ export function buildSite({ config, sqlTables } = {}) {
1154
1199
  gates,
1155
1200
  stats: null,
1156
1201
  tableErrors: [],
1202
+ expressionErrors: [],
1157
1203
  secretErrors: [],
1204
+ captionErrors: [],
1158
1205
  wikiErrors: [],
1159
1206
  imageErrors: [],
1160
1207
  mapFindings: [],
@@ -1164,6 +1211,7 @@ export function buildSite({ config, sqlTables } = {}) {
1164
1211
  const pass = resolveSitePass(site.pass, {
1165
1212
  ...site.passOptions,
1166
1213
  repoRoot: resolved.rootDir,
1214
+ config: resolved,
1167
1215
  });
1168
1216
 
1169
1217
  // The map from each related place, drawn now that every page is known to
@@ -1209,7 +1257,9 @@ export function buildSite({ config, sqlTables } = {}) {
1209
1257
  return {
1210
1258
  gates,
1211
1259
  tableErrors: rendered.tableErrors,
1260
+ expressionErrors: rendered.expressionErrors,
1212
1261
  secretErrors: rendered.secretErrors,
1262
+ captionErrors: rendered.captionErrors,
1213
1263
  wikiErrors: rendered.wikiErrors,
1214
1264
  imageErrors: rendered.imageErrors,
1215
1265
  mapFindings: drawn.findings,
@@ -14,7 +14,7 @@
14
14
  /**
15
15
  * The Hugo configuration a site build generates.
16
16
  *
17
- * `content-build site` writes the whole Hugo source tree under `build/hugo/`
17
+ * `package-build site` writes the whole Hugo source tree under `build/hugo/`
18
18
  * — `hugo.toml`, the content mount, Hugo's own cache — as a sibling of the
19
19
  * deployment root `build/site/`, so nothing Hugo reads lands in what is
20
20
  * published. The consumer's script runs Hugo over it; the toolchain never
@@ -50,7 +50,7 @@ import { slugify } from "./content-slug.mjs";
50
50
  /** The Hugo source directory, relative to the repository root. */
51
51
  export const HUGO_SOURCE = "build/hugo";
52
52
 
53
- /** The content mount `content-build site` writes, relative to the repository root. */
53
+ /** The content mount `package-build site` writes, relative to the repository root. */
54
54
  export const HUGO_CONTENT = `${HUGO_SOURCE}/content`;
55
55
 
56
56
  /**
@@ -255,7 +255,7 @@ export function readCachedNavigation(config) {
255
255
  const file = path.join(dir, NAVIGATION_FILE);
256
256
  if (!fs.existsSync(path.join(dir, STAMP)) || !fs.existsSync(file)) {
257
257
  throw new Error(
258
- "the site navigation has not been fetched. Run `content-build deps fetch` first.",
258
+ "the site navigation has not been fetched. Run `package-build deps fetch` first.",
259
259
  );
260
260
  }
261
261
  return checkNavigation(JSON.parse(fs.readFileSync(file, "utf8")), file);
@@ -364,17 +364,17 @@ function deepMerge(base, overrides) {
364
364
  * @param {string} options.themesDir - From {@link resolveThemesDir}.
365
365
  * @returns {Record<string, any>} The configuration Hugo reads.
366
366
  * @throws {TypeError} When `homepage` fails `checkHomepage`, or the
367
- * configuration declares no `packageBuild.manifest.title`, no
367
+ * configuration declares no `site.title` or `packageBuild.manifest.title`, no
368
368
  * `site.description`, or no `site.assets`.
369
369
  */
370
370
  export function hugoConfig({ config, navigation, themesDir }) {
371
371
  checkHomepage(config.homepage, config.contentPackage);
372
372
 
373
- const title = config.packageBuild?.manifest?.title;
373
+ const title = config.site.title || config.packageBuild?.manifest?.title;
374
374
  if (typeof title !== "string" || !title.trim()) {
375
375
  throw new TypeError(
376
- "package-build config: `packageBuild.manifest.title` is not declared, " +
377
- "and the site's `title` reads from it.",
376
+ "package-build config: declare `site.title` or " +
377
+ "`packageBuild.manifest.title` for the site's title.",
378
378
  );
379
379
  }
380
380
  if (!config.site.description) {
@@ -423,7 +423,7 @@ export function hugoConfig({ config, navigation, themesDir }) {
423
423
  */
424
424
  export function hugoToml(generated) {
425
425
  return (
426
- "# Generated by `content-build site` from package.json and " +
426
+ "# Generated by `package-build site` from package.json and " +
427
427
  "package-build.config.yaml.\n# Every value here has a source there; edit " +
428
428
  "the source, not this file.\n\n" +
429
429
  `${stringifyToml(generated)}\n`
@@ -65,6 +65,7 @@ import {
65
65
  } from "./address.mjs";
66
66
  import { NOTE_SYSTEM } from "./systems.mjs";
67
67
  import { contentPackage } from "./content-package.mjs";
68
+ import { reckoningContext } from "./reckoning-markers.mjs";
68
69
  // The declared tag vocabulary, which is where `draft` is stated.
69
70
  import { isDraftNote } from "./note-vocabulary.mjs";
70
71
 
@@ -160,6 +161,8 @@ function mergeForeign(index, foreignIndex) {
160
161
  * `contentIndex: false` — a Foundry dependency only, with no fetched index.
161
162
  * A link naming one fails naming the key, rather than reading as prose or an
162
163
  * ordinary dead address.
164
+ * @param {object[]} [options.records] - Content-index records.
165
+ * @param {Map<string, object>} [options.foreignReferences] - Foreign reference targets.
163
166
  * @returns {SiteIndex} The index, and what could not be addressed unambiguously.
164
167
  */
165
168
  export function buildSiteIndex(
@@ -252,6 +255,7 @@ export function buildSiteIndex(
252
255
 
253
256
  return {
254
257
  contentPackage: ownPackage,
258
+ dateContext: reckoningContext({ notes: records }),
255
259
  referenceTargets: buildReferenceTargets(records, new Map([...foreignReferences, ...index])),
256
260
  index,
257
261
  ambiguous,
@@ -15,7 +15,7 @@
15
15
  * The maps a site build writes into its place pages.
16
16
  *
17
17
  * A place that states, or is named in, a border or a route has a map from it
18
- * — the drawing `content-build map --from` makes — and the site build draws
18
+ * — the drawing `package-build map --from` makes — and the site build draws
19
19
  * that map for every such place of this package and hands each one to the
20
20
  * page writer, which lays it beside the page as `from-<shortcode>.svg` and
21
21
  * names it in the front matter as `map`. The theme inlines it, so its place
@@ -18,6 +18,7 @@ import { reckoningContext } from "./reckoning-markers.mjs";
18
18
  import { cachedMetadataIndexes, noContentIndexPackages } from "./metadata-index.mjs";
19
19
  import { buildSiteIndex } from "./site-index.mjs";
20
20
  import { openNotesDatabase, prepareSqlTables, findSqlBlocks } from "./sql-tables.mjs";
21
+ import { isGmNote } from "./note-vocabulary.mjs";
21
22
  import { relatedPages } from "./related-pages.mjs";
22
23
  import { holdingsNode, holdingsPages, foreignHoldingsNodes } from "./holdings.mjs";
23
24
  import { assetAddressIndex } from "./art-fields.mjs";
@@ -79,10 +80,14 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
79
80
  const gates = siteGates([...pages, ...homeEntries], findings, { config, records });
80
81
  if (gatesFailed(gates))
81
82
  throw new Error("site preview cannot prepare: site integrity gates failed");
82
- const db = await openNotesDatabase(records, {
83
- dependencies,
84
- addressContext: noteAddressContext(config),
85
- });
83
+ const db = await openNotesDatabase(
84
+ records.filter((record) => !isGmNote(record)),
85
+ {
86
+ dependencies,
87
+ addressContext: noteAddressContext(config),
88
+ audience: "public",
89
+ },
90
+ );
86
91
  try {
87
92
  const sources = pages
88
93
  .filter((page) => findSqlBlocks(page.body).length)
@@ -200,10 +205,14 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
200
205
  records,
201
206
  noIndexPackages: noContentIndexPackages(config),
202
207
  });
203
- db = await openNotesDatabase(records, {
204
- dependencies,
205
- addressContext: noteAddressContext(config),
206
- });
208
+ db = await openNotesDatabase(
209
+ records.filter((record) => !isGmNote(record)),
210
+ {
211
+ dependencies,
212
+ addressContext: noteAddressContext(config),
213
+ audience: "public",
214
+ },
215
+ );
207
216
  }
208
217
  const sqlTables = await prepareSqlTables(db, [
209
218
  { source: absolute, markdown: body },
@@ -235,6 +244,8 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
235
244
  });
236
245
  for (const error of result.secretErrors)
237
246
  findings.push({ ...error, severity: "error" });
247
+ for (const error of result.captionErrors)
248
+ findings.push({ ...error, severity: "error" });
238
249
  for (const error of result.wikiErrors) {
239
250
  const pos = positionOfLiteral(text, error.link, error.occurrence);
240
251
  findings.push({
@@ -16,7 +16,11 @@ export { SOCIAL_TIES, SOCIAL_TIE_TARGET_TYPES };
16
16
 
17
17
  const TERMS = new Set(SOCIAL_TIES.map(({ term }) => term));
18
18
 
19
- /** Validate a being's map of defining relationships. */
19
+ /**
20
+ * Validate a being's map of defining relationships.
21
+ * @param {object} note
22
+ * @param {{index?: object}} [options]
23
+ */
20
24
  export function checkSocialTies(note, { index } = {}) {
21
25
  const ties = note.fm?.data?.socialTies;
22
26
  if (ties === undefined || ties === null) return [];