@heroiclands/package-build 23.12.8 → 24.0.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 (258) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +3 -2
  3. package/assets/preview/gitea.ini +12 -7
  4. package/assets/styles/content-base.css +293 -24
  5. package/bin/content-commands.mjs +370 -213
  6. package/bin/package-build-preview.mjs +2 -2
  7. package/bin/package-build.mjs +3 -2
  8. package/ci/check-banners.mjs +7 -7
  9. package/config.mjs +21 -6
  10. package/content-config.mjs +199 -80
  11. package/docs/api.md +110 -89
  12. package/docs/authoring/links-and-markup.md +469 -97
  13. package/docs/commands.md +259 -78
  14. package/docs/configuration.md +211 -103
  15. package/docs/content-format.md +4 -4
  16. package/docs/diagnostics.md +44 -2
  17. package/docs/getting-started.md +1 -1
  18. package/docs/guides/icons.md +1 -1
  19. package/docs/guides/module-project.md +5 -8
  20. package/docs/guides/release-and-deploy.md +2 -2
  21. package/docs/guides/site.md +112 -23
  22. package/docs/guides/system-project.md +1 -1
  23. package/docs/guides/writing-in-invented-scripts.md +1247 -0
  24. package/docs/index.md +1 -0
  25. package/docs/project-setup.md +28 -30
  26. package/docs/reference/format-details.md +677 -319
  27. package/docs/reference/note-types.md +47 -10
  28. package/docs/reference/theme-pages.md +178 -108
  29. package/docs/source-preview.md +16 -2
  30. package/engine/anchors.mjs +2 -2
  31. package/engine/asset-shipping.mjs +22 -10
  32. package/engine/base-compiler.mjs +62 -104
  33. package/engine/changelog-lint.mjs +1 -1
  34. package/engine/code-fences.mjs +3 -3
  35. package/engine/content-address.mjs +1 -1
  36. package/engine/content-alerts.mjs +8 -4
  37. package/engine/content-blocks.mjs +29 -14
  38. package/engine/content-embeds.mjs +1 -1
  39. package/engine/content-expand.mjs +376 -0
  40. package/engine/content-figures.mjs +18 -11
  41. package/engine/content-format.yaml +8 -0
  42. package/engine/content-glyphs.mjs +213 -0
  43. package/engine/content-images.mjs +6 -7
  44. package/engine/content-index.mjs +1 -1
  45. package/engine/content-links.mjs +3 -3
  46. package/engine/content-lint.mjs +1 -1
  47. package/engine/content-render.mjs +453 -0
  48. package/engine/content-scripts.mjs +376 -43
  49. package/engine/content-spans.mjs +6 -3
  50. package/engine/content-styles.mjs +91 -0
  51. package/engine/content-symbols.mjs +186 -0
  52. package/engine/content-verse.mjs +738 -0
  53. package/engine/core-report.mjs +169 -0
  54. package/engine/core-vocabulary.mjs +208 -0
  55. package/engine/dictionary-html.mjs +119 -0
  56. package/engine/dictionary-rows.mjs +15 -4
  57. package/engine/field-reference.mjs +5 -5
  58. package/engine/first-use.mjs +17 -27
  59. package/engine/frontmatter-lint.mjs +1 -1
  60. package/engine/heading-attributes.mjs +89 -0
  61. package/engine/helpers.mjs +61 -101
  62. package/engine/homepage.mjs +17 -29
  63. package/engine/html-minify.mjs +70 -0
  64. package/engine/index.mjs +8 -5
  65. package/engine/key-case-collisions.mjs +136 -0
  66. package/engine/language-data.mjs +203 -6
  67. package/engine/language-rows.mjs +8 -2
  68. package/engine/language-terms.mjs +186 -16
  69. package/engine/language-words.mjs +12 -0
  70. package/engine/logographic.mjs +255 -0
  71. package/engine/map-lexicon.mjs +262 -47
  72. package/engine/map-places.mjs +5 -4
  73. package/engine/markdown-expressions.mjs +0 -2
  74. package/engine/markdownlint-run.mjs +9 -3
  75. package/engine/markup-validation.mjs +21 -0
  76. package/engine/metre-declaration.mjs +134 -0
  77. package/engine/names.mjs +36 -9
  78. package/engine/note-matter.mjs +66 -0
  79. package/engine/note-vocabulary.mjs +187 -3
  80. package/engine/pdf-build.mjs +69 -133
  81. package/engine/pdf-render.mjs +205 -58
  82. package/engine/post-fields.mjs +157 -0
  83. package/engine/preview-index.mjs +130 -0
  84. package/engine/preview-source.mjs +450 -0
  85. package/engine/project-init.mjs +5 -12
  86. package/engine/prose-config.mjs +3 -3
  87. package/engine/read-stdin.mjs +29 -0
  88. package/engine/run-rows.mjs +2 -2
  89. package/engine/run-words.mjs +6 -112
  90. package/engine/script-emphasis-terms.mjs +38 -0
  91. package/engine/script-emphasis.mjs +295 -0
  92. package/engine/script-font-delivery.mjs +4 -4
  93. package/engine/site-build.mjs +260 -315
  94. package/engine/site-config.mjs +26 -238
  95. package/engine/site-files.mjs +175 -0
  96. package/engine/site-index.mjs +9 -4
  97. package/engine/site-preview.mjs +52 -525
  98. package/engine/site-render.mjs +593 -0
  99. package/engine/site-root.mjs +22 -13
  100. package/engine/site-search.mjs +161 -0
  101. package/engine/site-serve.mjs +273 -0
  102. package/engine/site-theme-banner.mjs +154 -0
  103. package/engine/site-theme-urls.mjs +125 -0
  104. package/engine/site-theme.mjs +926 -0
  105. package/engine/sql-tables.mjs +20 -3
  106. package/engine/text-maps.mjs +57 -10
  107. package/engine/typographer.mjs +248 -0
  108. package/engine/verse-metre.mjs +357 -0
  109. package/engine/wikilink-syntax.mjs +29 -47
  110. package/engine/wikilinks.mjs +758 -279
  111. package/engine/word-forms.mjs +364 -0
  112. package/engine/word-rows.mjs +12 -2
  113. package/engine/word-terms.mjs +38 -4
  114. package/manifest.mjs +3 -4
  115. package/package.json +11 -8
  116. package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
  117. package/site-theme/layouts/base.hbs +31 -0
  118. package/site-theme/layouts/blog-list.hbs +30 -0
  119. package/site-theme/layouts/blog-post.hbs +40 -0
  120. package/site-theme/layouts/home.hbs +35 -0
  121. package/site-theme/layouts/list.hbs +20 -0
  122. package/site-theme/layouts/not-found.hbs +31 -0
  123. package/site-theme/layouts/page.hbs +7 -0
  124. package/site-theme/partials/copy-buttons.hbs +37 -0
  125. package/site-theme/partials/draft-notice.hbs +5 -0
  126. package/site-theme/partials/exturl.hbs +2 -0
  127. package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
  128. package/site-theme/partials/header.hbs +44 -0
  129. package/site-theme/partials/hero-banner.hbs +11 -0
  130. package/site-theme/partials/infobox-entry.hbs +2 -0
  131. package/site-theme/partials/infobox-section.hbs +31 -0
  132. package/site-theme/partials/infobox-value.hbs +2 -0
  133. package/site-theme/partials/infobox.hbs +18 -0
  134. package/site-theme/partials/list-members.hbs +9 -0
  135. package/site-theme/partials/nav-links.hbs +5 -0
  136. package/site-theme/partials/page-body.hbs +48 -0
  137. package/site-theme/partials/page-list.hbs +14 -0
  138. package/site-theme/partials/related.hbs +24 -0
  139. package/site-theme/partials/search-meta.hbs +6 -0
  140. package/site-theme/partials/search-names.hbs +2 -0
  141. package/site-theme/partials/search.hbs +52 -0
  142. package/site-theme/partials/tags.hbs +4 -0
  143. package/site-theme/partials/toc.hbs +7 -0
  144. package/{hugo-theme → site-theme}/static/css/style.css +106 -14
  145. package/stage.mjs +1 -2
  146. package/types/config.d.mts +8 -4
  147. package/types/content-config.d.mts +15 -15
  148. package/types/engine/asset-shipping.d.mts +5 -2
  149. package/types/engine/base-compiler.d.mts +20 -2
  150. package/types/engine/code-fences.d.mts +3 -3
  151. package/types/engine/content-address.d.mts +1 -1
  152. package/types/engine/content-alerts.d.mts +8 -2
  153. package/types/engine/content-blocks.d.mts +11 -4
  154. package/types/engine/content-expand.d.mts +288 -0
  155. package/types/engine/content-figures.d.mts +11 -3
  156. package/types/engine/content-glyphs.d.mts +85 -0
  157. package/types/engine/content-images.d.mts +6 -7
  158. package/types/engine/content-links.d.mts +1 -1
  159. package/types/engine/content-render.d.mts +199 -0
  160. package/types/engine/content-scripts.d.mts +70 -4
  161. package/types/engine/content-spans.d.mts +1 -1
  162. package/types/engine/content-styles.d.mts +33 -0
  163. package/types/engine/content-symbols.d.mts +35 -0
  164. package/types/engine/content-verse.d.mts +240 -0
  165. package/types/engine/core-report.d.mts +45 -0
  166. package/types/engine/core-vocabulary.d.mts +53 -0
  167. package/types/engine/dictionary-html.d.mts +8 -0
  168. package/types/engine/dictionary-rows.d.mts +10 -0
  169. package/types/engine/field-reference.d.mts +2 -2
  170. package/types/engine/heading-attributes.d.mts +20 -0
  171. package/types/engine/helpers.d.mts +3 -10
  172. package/types/engine/homepage.d.mts +11 -24
  173. package/types/engine/html-minify.d.mts +9 -0
  174. package/types/engine/index.d.mts +2 -1
  175. package/types/engine/key-case-collisions.d.mts +28 -0
  176. package/types/engine/language-data.d.mts +0 -2
  177. package/types/engine/language-terms.d.mts +24 -2
  178. package/types/engine/logographic.d.mts +45 -0
  179. package/types/engine/map-lexicon.d.mts +66 -13
  180. package/types/engine/map-places.d.mts +8 -0
  181. package/types/engine/markdown-expressions.d.mts +0 -2
  182. package/types/engine/metre-declaration.d.mts +39 -0
  183. package/types/engine/names.d.mts +34 -3
  184. package/types/engine/note-matter.d.mts +48 -0
  185. package/types/engine/pdf-render.d.mts +6 -2
  186. package/types/engine/post-fields.d.mts +44 -0
  187. package/types/engine/preview-index.d.mts +72 -0
  188. package/types/engine/preview-source.d.mts +51 -0
  189. package/types/engine/read-stdin.d.mts +11 -0
  190. package/types/engine/run-rows.d.mts +2 -2
  191. package/types/engine/run-words.d.mts +0 -15
  192. package/types/engine/script-emphasis-terms.d.mts +21 -0
  193. package/types/engine/script-emphasis.d.mts +84 -0
  194. package/types/engine/script-font-delivery.d.mts +4 -4
  195. package/types/engine/site-build.d.mts +71 -113
  196. package/types/engine/site-config.d.mts +14 -134
  197. package/types/engine/site-files.d.mts +73 -0
  198. package/types/engine/site-index.d.mts +11 -3
  199. package/types/engine/site-preview.d.mts +15 -19
  200. package/types/engine/site-render.d.mts +171 -0
  201. package/types/engine/site-root.d.mts +12 -4
  202. package/types/engine/site-search.d.mts +54 -0
  203. package/types/engine/site-serve.d.mts +44 -0
  204. package/types/engine/site-theme-banner.d.mts +51 -0
  205. package/types/engine/site-theme-urls.d.mts +60 -0
  206. package/types/engine/site-theme.d.mts +127 -0
  207. package/types/engine/sql-tables.d.mts +4 -1
  208. package/types/engine/text-maps.d.mts +17 -3
  209. package/types/engine/typographer.d.mts +11 -0
  210. package/types/engine/verse-metre.d.mts +131 -0
  211. package/types/engine/wikilink-syntax.d.mts +30 -48
  212. package/types/engine/wikilinks.d.mts +330 -14
  213. package/types/engine/word-forms.d.mts +83 -0
  214. package/types/manifest.d.mts +3 -4
  215. package/types/stage.d.mts +1 -2
  216. package/types/vendor/markdownlint.d.mts +8 -0
  217. package/vendor/markdownlint.mjs +20388 -0
  218. package/engine/content-poetry.mjs +0 -240
  219. package/engine/web-wikilinks.mjs +0 -594
  220. package/hugo-theme/layouts/404.html +0 -85
  221. package/hugo-theme/layouts/_default/baseof.html +0 -67
  222. package/hugo-theme/layouts/_default/list.html +0 -139
  223. package/hugo-theme/layouts/_default/single.html +0 -4
  224. package/hugo-theme/layouts/blog/list.html +0 -54
  225. package/hugo-theme/layouts/blog-post/single.html +0 -36
  226. package/hugo-theme/layouts/index.html +0 -75
  227. package/hugo-theme/layouts/partials/banner-url.html +0 -141
  228. package/hugo-theme/layouts/partials/cdn-url.html +0 -36
  229. package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
  230. package/hugo-theme/layouts/partials/draft-notice.html +0 -41
  231. package/hugo-theme/layouts/partials/draft-state.html +0 -26
  232. package/hugo-theme/layouts/partials/header.html +0 -35
  233. package/hugo-theme/layouts/partials/hero-banner.html +0 -85
  234. package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
  235. package/hugo-theme/layouts/partials/infobox/items.html +0 -26
  236. package/hugo-theme/layouts/partials/infobox/section.html +0 -56
  237. package/hugo-theme/layouts/partials/infobox/value.html +0 -43
  238. package/hugo-theme/layouts/partials/infobox.html +0 -57
  239. package/hugo-theme/layouts/partials/page-body.html +0 -152
  240. package/hugo-theme/layouts/partials/related.html +0 -138
  241. package/hugo-theme/layouts/partials/search.html +0 -84
  242. package/hugo-theme/layouts/partials/site-url.html +0 -32
  243. package/hugo-theme/layouts/partials/tags.html +0 -30
  244. package/hugo-theme/layouts/partials/toc.html +0 -28
  245. package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
  246. package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
  247. package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
  248. package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
  249. package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
  250. package/hugo-theme/theme.toml +0 -11
  251. package/types/engine/content-poetry.d.mts +0 -58
  252. package/types/engine/web-wikilinks.d.mts +0 -107
  253. /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
  254. /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
  255. /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
  256. /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
  257. /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
  258. /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
@@ -31,7 +31,7 @@
31
31
  * npx package-build map [--tree] [--from <shortcode>] [--chart <shortcode>] [--travel] [--out <dir>]
32
32
  * npx package-build reachability <dir> [file] [--index <shortcode>]
33
33
  * npx package-build transliterate <map> [--lang <language>] [--font <slug>] [--hex] [text..]
34
- * npx package-build lexicon <language> --map <map> [--font <slug>] [--json]
34
+ * npx package-build lexicon <language> --map <map> [--font <slug>] [--derived] [--json]
35
35
  * npx package-build addresses diff --from <zip|dir> [--strict]
36
36
  * npx package-build npc new (--spec <json> | --specfile <path>) [--seed <text>] [--out <path>]
37
37
  *
@@ -45,8 +45,9 @@ import fs from "fs";
45
45
  import path from "node:path";
46
46
  import log from "loglevel";
47
47
  import prefix from "loglevel-plugin-prefix";
48
+ import { readStdin } from "../engine/read-stdin.mjs";
48
49
  import { compilePacks, cleanPacks, unpackPacks } from "../engine/compendiums.mjs";
49
- import { compilesFoundryDocuments, publishesContentPages } from "../content-config.mjs";
50
+ import { compilesFoundryDocuments, publishesContentPages, siteBase } from "../content-config.mjs";
50
51
  import { loadPackConfig, packConfigPath } from "../engine/pack-config.mjs";
51
52
  import { buildPdf, listBookAssets } from "../engine/pdf-build.mjs";
52
53
  import { summaryLines } from "../engine/pdf-coverage.mjs";
@@ -64,19 +65,7 @@ import {
64
65
  unaddressableForeignPackages,
65
66
  formatUnaddressableFinding,
66
67
  } from "../engine/metadata-index.mjs";
67
- import {
68
- fetchNavigation,
69
- generateHugoConfig,
70
- HUGO_SOURCE,
71
- writeHugoConfig,
72
- } from "../engine/site-config.mjs";
73
- import {
74
- checkScriptFonts,
75
- copyScriptFonts,
76
- scriptFontFiles,
77
- SITE_FONTS_DIR,
78
- siteFontsDestination,
79
- } from "../engine/script-font-delivery.mjs";
68
+ import { DEPLOY_ROOT, fetchNavigation } from "../engine/site-config.mjs";
80
69
  import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
81
70
  import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
82
71
  import { checkArtSlotSizes } from "../engine/art-fields.mjs";
@@ -117,6 +106,8 @@ import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
117
106
  import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
118
107
  import { markupFindings } from "../engine/markup-validation.mjs";
119
108
  import { runContext } from "../engine/content-scripts.mjs";
109
+ import { coreReport, coreReportText } from "../engine/core-report.mjs";
110
+ import { checkScriptNoteFonts } from "../engine/script-emphasis.mjs";
120
111
  import {
121
112
  checkLexiconNatives,
122
113
  checkMapFonts,
@@ -136,8 +127,12 @@ import {
136
127
  import {
137
128
  buildSite,
138
129
  gatesFailed,
130
+ siteBuildFailed,
139
131
  formatUnaddressableFinding as formatUnaddressable,
140
132
  } from "../engine/site-build.mjs";
133
+ import { buildPackageSite, siteDirectory } from "../engine/site-render.mjs";
134
+ import { serveSite } from "../engine/site-serve.mjs";
135
+ import { writeSiteRoot } from "../engine/site-root.mjs";
141
136
  import { auditLinks, buildLinkIndex, walkReachability } from "../engine/content-links.mjs";
142
137
  import { prepareLinkSqlTables, prepareTreeSqlTables } from "../engine/sql-tables.mjs";
143
138
  import { createContentSession, CONTENT_TARGETS } from "../engine/content-session.mjs";
@@ -324,6 +319,7 @@ export function registerContentCommands(cli) {
324
319
  .command(withIndexPreflight(reachabilityCommand()))
325
320
  .command(transliterateCommand())
326
321
  .command(lexiconCommand())
322
+ .command(coreCommand())
327
323
  .command(addressesCommand())
328
324
  .command(withIndexPreflight(npcCommand()));
329
325
  }
@@ -1292,6 +1288,7 @@ function lintCommand() {
1292
1288
  // Each mapping note's faces, each language's native forms
1293
1289
  // and each language's `note`, against the faces and maps.
1294
1290
  ...index.notes.flatMap((note) => checkMapFonts(note, runs)),
1291
+ ...index.notes.flatMap((note) => checkScriptNoteFonts(note, runs)),
1295
1292
  ...index.notes.flatMap((note) => checkLexiconNatives(note, runs)),
1296
1293
  ...languageNoteFindings({ ...config, configFile: packConfigPath() }, records),
1297
1294
  ...index.notes.flatMap(firstUseChecker),
@@ -1925,60 +1922,123 @@ function pdfCommand() {
1925
1922
  }
1926
1923
 
1927
1924
  /**
1928
- * Copy the installed script fonts into the Hugo static tree, under a path that
1929
- * names their version, and drop any other version's copy.
1925
+ * Report what a site build found, in the order {@link siteCommand} sets out:
1926
+ * the gates first, stopping at the first that fired, then each note's findings,
1927
+ * then what drawing the maps found.
1930
1928
  *
1931
- * A package that has not installed `@heroiclands/fonts`, or sets no text in
1932
- * one of its faces, gets no copy; a package missing a file its fonts depend on
1933
- * gets a located error and a non-zero exit.
1934
- *
1935
- * @param {object} config - The resolved content configuration.
1936
- * @returns {void}
1929
+ * @param {object} result - From `buildSite`.
1930
+ * @returns {boolean} Whether the build may be written: no gate fired and no
1931
+ * note is in error.
1937
1932
  */
1938
- function stageSiteFonts(config) {
1939
- const root = path.join(config.rootDir, HUGO_SOURCE, "static", SITE_FONTS_DIR);
1940
- fs.rmSync(root, { recursive: true, force: true });
1941
- const found = scriptFontFiles(config, ["woff2"]);
1942
- if (!found) return;
1943
- const findings = checkScriptFonts(config);
1944
- for (const finding of findings) emitDiagnostic(finding);
1945
- if (findings.length) {
1946
- process.exitCode = 1;
1947
- return;
1933
+ function reportSiteBuild(result) {
1934
+ const { gates } = result;
1935
+
1936
+ // First, because it is decided before the tree is walked and
1937
+ // before the output is cleared: a package with no front page,
1938
+ // or two competing for it, has nothing to say about its pages
1939
+ // yet.
1940
+ for (const f of gates.homepages) emitDiagnostic(f);
1941
+ for (const f of gates.frontmatterLinks) {
1942
+ emitDiagnostic({
1943
+ file: f.file,
1944
+ severity: "error",
1945
+ message:
1946
+ `wikilink ${f.link} authored in frontmatter at ` +
1947
+ `${f.path} — frontmatter is data, is copied to the ` +
1948
+ `page verbatim, and reaches the reader as brackets`,
1949
+ });
1948
1950
  }
1949
- const dest = path.join(
1950
- config.rootDir,
1951
- HUGO_SOURCE,
1952
- "static",
1953
- siteFontsDestination(found.version),
1954
- );
1955
- const count = copyScriptFonts(config, dest, ["woff2"]);
1956
- log.info(`wrote ${count} script font file(s) to ${path.relative(process.cwd(), dest)}`);
1951
+ for (const f of gates.addressErrors) {
1952
+ emitDiagnostic({
1953
+ file: f.file,
1954
+ severity: "error",
1955
+ message: `cannot derive a URL: ${f.reason}`,
1956
+ });
1957
+ }
1958
+ reportSiteFindings(gates.sections ?? []);
1959
+ for (const s of gates.staleManifests) {
1960
+ emitDiagnostic({
1961
+ file: cachedIndexPath(loadPackConfig(), s.package),
1962
+ severity: "error",
1963
+ message: `unusable content index: ${s.reason}`,
1964
+ });
1965
+ }
1966
+ for (const f of gates.unaddressable) {
1967
+ console.error(formatUnaddressable(f, loadPackConfig()));
1968
+ }
1969
+ if (gatesFailed(gates)) {
1970
+ return false;
1971
+ }
1972
+
1973
+ // Every note's findings are reported together: stopping at the first
1974
+ // would make a single bad table hide every other problem in the tree.
1975
+ // Each is `file:line:column: error: message`, path first — the shape
1976
+ // the pack build reports the same findings in.
1977
+ for (const e of result.tableErrors) {
1978
+ emitDiagnostic({
1979
+ file: e.source,
1980
+ line: typeof e.line === "number" ? e.line + 1 : undefined,
1981
+ column: e.column,
1982
+ severity: "error",
1983
+ message: e.reason,
1984
+ });
1985
+ }
1986
+ for (const e of result.expressionErrors) {
1987
+ emitDiagnostic(e);
1988
+ }
1989
+ for (const e of result.secretErrors) {
1990
+ emitDiagnostic({ ...e, severity: "error" });
1991
+ }
1992
+ for (const e of result.captionErrors) {
1993
+ emitDiagnostic({ ...e, severity: "error" });
1994
+ }
1995
+ for (const e of result.headingErrors) {
1996
+ emitDiagnostic({ ...e, severity: "error" });
1997
+ }
1998
+ for (const e of result.footnoteErrors) {
1999
+ emitDiagnostic({ ...e, severity: "error" });
2000
+ }
2001
+ for (const e of result.embedErrors) {
2002
+ emitDiagnostic({ ...e, severity: "error" });
2003
+ }
2004
+ // Worded by the shared table the pack build words the same finding
2005
+ // with, and located by searching the note for the link.
2006
+ for (const e of result.wikiErrors) {
2007
+ emitDiagnostic({
2008
+ file: e.file,
2009
+ ...positionOfLiteral(readRawNote(e.file), e.link, e.occurrence),
2010
+ severity: "error",
2011
+ // An embed's directive speaks the image vocabulary
2012
+ // rather than the link one, and carries its own
2013
+ // sentence; everything else is worded by the shared
2014
+ // table.
2015
+ message: e.message ?? linkFindingMessage(e),
2016
+ });
2017
+ }
2018
+ // An image whose pathname the site cannot resolve, located the
2019
+ // way a wikilink finding is: by searching the note for the
2020
+ // literal the resolver was handed.
2021
+ for (const e of result.imageErrors) {
2022
+ emitDiagnostic({
2023
+ file: e.file,
2024
+ ...positionOfLiteral(readRawNote(e.file), e.src, e.occurrence),
2025
+ severity: "error",
2026
+ message: e.message,
2027
+ });
2028
+ }
2029
+ if (siteBuildFailed(result)) return false;
2030
+
2031
+ // Relation warnings do not fail a site build.
2032
+ for (const f of result.mapFindings) emitDiagnostic(f);
2033
+ return true;
1957
2034
  }
1958
2035
 
1959
2036
  /**
1960
- * `package-build site` — publish the content tree as a website.
2037
+ * `package-build site` — the website commands.
1961
2038
  *
1962
2039
  * The sibling of `package compile`: the same tree, rendered as pages instead of
1963
- * compiled into packs. Everything a consumer would otherwise write for itself —
1964
- * the walk, the address derivation, the address index, table expansion,
1965
- * wikilink resolution, code-fence protection and the foreign-manifest merge —
1966
- * happens here, from configuration. So does the Hugo configuration: the whole
1967
- * source tree Hugo reads lands under `build/hugo/`, and the consumer's script
1968
- * runs Hugo over it. With `site.maps` on, every place
1969
- * page with a border or a route carries the map from that place.
1970
- *
1971
- * **The Hugo configuration is generated before anything is written.** Its
1972
- * sources — `package.json`'s `homepage`, the cached navigation, the installed
1973
- * theme — are each a way the build can fail, and failing before the output
1974
- * tree is cleared leaves the last good site in place to be looked at. Nothing
1975
- * the walk reads changes it: a site renders its homepage and its pages, and
1976
- * the kinds Hugo disables are the same on every site.
1977
- *
1978
- * **Each gate is reported and the run stops at the first that fires.** They are
1979
- * ordered so the report names the cause rather than its symptoms: an unusable
1980
- * manifest, reported after the links that failed because of it, reads as a pile
1981
- * of broken notes.
2040
+ * compiled into packs. `site build` renders the tree and `site serve` serves it
2041
+ * with live rebuilds; a bare `site` names them.
1982
2042
  *
1983
2043
  * @returns {object} The yargs command module.
1984
2044
  */
@@ -1986,163 +2046,185 @@ function stageSiteFonts(config) {
1986
2046
  function siteCommand() {
1987
2047
  return {
1988
2048
  command: "site",
1989
- describe: "Build the Hugo source tree from the content tree",
1990
- builder: () => {},
1991
- handler: async () => {
2049
+ describe: "Render the content tree as a website",
2050
+ builder: (yargs) =>
2051
+ yargs
2052
+ .command(
2053
+ withIndexPreflight(
2054
+ withBodyPreflight(siteBuildCommand()),
2055
+ publishesContentPages,
2056
+ ),
2057
+ )
2058
+ .command(
2059
+ withIndexPreflight(
2060
+ withBodyPreflight(siteServeCommand()),
2061
+ publishesContentPages,
2062
+ ),
2063
+ )
2064
+ .demandCommand(1, "Name a site command: build or serve."),
2065
+ };
2066
+ }
2067
+
2068
+ /**
2069
+ * Report what the layouts, or the build's section gate, found about a site.
2070
+ *
2071
+ * A finding about a site parameter is located at the configuration's
2072
+ * `site.<param>` entry; one about a page, at the note the page was written
2073
+ * from.
2074
+ *
2075
+ * @param {readonly object[]} findings - The findings.
2076
+ */
2077
+ function reportSiteFindings(findings) {
2078
+ const configFile = packConfigPath();
2079
+ const configText = fs.existsSync(configFile) ? fs.readFileSync(configFile, "utf8") : "";
2080
+ for (const finding of findings) {
2081
+ if (finding.param) {
2082
+ emitDiagnostic({
2083
+ file: path.relative(process.cwd(), configFile),
2084
+ ...positionOfYamlPath(configText, ["site", ...finding.param]),
2085
+ severity: finding.severity,
2086
+ message: finding.message,
2087
+ });
2088
+ } else {
2089
+ emitDiagnostic({
2090
+ file: finding.file ?? finding.page,
2091
+ severity: finding.severity,
2092
+ message: finding.message,
2093
+ });
2094
+ }
2095
+ }
2096
+ }
2097
+
2098
+ /**
2099
+ * `package-build site build` — render the content tree as a website.
2100
+ *
2101
+ * Every page is drawn by the site theme from the shared renderer's HTML and
2102
+ * written under `<out>/<package>/`, with `404.html`, `sitemap.xml`, the
2103
+ * theme's stylesheets, scripts and fonts and the package's script fonts.
2104
+ * `_headers` is then written beside it and the search index built inside it,
2105
+ * as `package-build site-root` does. The site's settings and cached menu are
2106
+ * read before the tree, so a site that cannot be described fails with the
2107
+ * last good one still in place.
2108
+ *
2109
+ * @returns {object} The yargs command module.
2110
+ */
2111
+ function siteBuildCommand() {
2112
+ return {
2113
+ command: "build",
2114
+ describe: "Render the content tree as a website and index it for search",
2115
+ builder: (yargs) =>
2116
+ yargs.option("out", {
2117
+ type: "string",
2118
+ describe: `The directory that is deployed (default: ${DEPLOY_ROOT})`,
2119
+ }),
2120
+ handler: async (argv) => {
1992
2121
  try {
1993
- const config = loadPackConfig();
1994
- // Generated before the walk, to fail fast on a missing or
1995
- // mismatched source — `homepage`, the manifest title, the
1996
- // cached navigation, the installed theme — while the last
1997
- // good site is still in place to be looked at. Written only
1998
- // once the walk has succeeded.
1999
- const hugo = generateHugoConfig(config);
2000
- const result = buildSite({
2001
- config,
2002
- sqlTables: await prepareTreeSqlTables(config.paths.content, {
2003
- skipDirectories: config.skipDirectories,
2004
- config,
2005
- audience: "public",
2006
- }),
2007
- });
2008
- const { gates } = result;
2009
-
2010
- // First, because it is decided before the tree is walked and
2011
- // before the output is cleared: a package with no front page,
2012
- // or two competing for it, has nothing to say about its pages
2013
- // yet.
2014
- for (const f of gates.homepages) emitDiagnostic(f);
2015
- for (const f of gates.frontmatterLinks) {
2016
- emitDiagnostic({
2017
- file: f.file,
2018
- severity: "error",
2019
- message:
2020
- `wikilink ${f.link} authored in frontmatter at ` +
2021
- `${f.path} — frontmatter is data, is copied to the ` +
2022
- `page verbatim, and reaches the reader as brackets`,
2023
- });
2024
- }
2025
- for (const f of gates.addressErrors) {
2026
- emitDiagnostic({
2027
- file: f.file,
2028
- severity: "error",
2029
- message: `cannot derive a URL: ${f.reason}`,
2030
- });
2031
- }
2032
- for (const s of gates.staleManifests) {
2033
- emitDiagnostic({
2034
- file: cachedIndexPath(loadPackConfig(), s.package),
2035
- severity: "error",
2036
- message: `unusable content index: ${s.reason}`,
2037
- });
2038
- }
2039
- for (const f of gates.unaddressable) {
2040
- console.error(formatUnaddressable(f, loadPackConfig()));
2041
- }
2042
- if (gatesFailed(gates)) {
2122
+ const config = argv[CONTENT_CONFIG] ?? loadPackConfig();
2123
+ const root = path.resolve(config.rootDir, argv.out ?? DEPLOY_ROOT);
2124
+ const out = siteDirectory(config, root);
2125
+ const result = await buildPackageSite({ config, out });
2126
+ // A note in error is written as its page shows it, and the
2127
+ // build fails without indexing a site that is not to be
2128
+ // deployed.
2129
+ if (!reportSiteBuild(result.built) || !result.written) {
2043
2130
  process.exitCode = 1;
2044
2131
  return;
2045
2132
  }
2046
-
2047
- // Reported after the write rather than before it: both are
2048
- // failures of individual notes, and stopping the whole build
2049
- // before anything is emitted would make a single bad table
2050
- // hide every other problem in the tree.
2051
- // `file:line:column: error: message`, path first — the same
2052
- // shape the pack build and the wikilink findings below use. It
2053
- // was prose with a timestamp where a parser reads the path,
2054
- // which made one authored table produce a machine-readable
2055
- // diagnostic from one build and something ungreppable from the
2056
- // other.
2057
- for (const e of result.tableErrors) {
2058
- emitDiagnostic({
2059
- file: e.source,
2060
- line: typeof e.line === "number" ? e.line + 1 : undefined,
2061
- column: e.column,
2062
- severity: "error",
2063
- message: e.reason,
2064
- });
2065
- }
2066
- for (const e of result.expressionErrors) {
2067
- emitDiagnostic(e);
2068
- }
2069
- for (const e of result.secretErrors) {
2070
- emitDiagnostic({ ...e, severity: "error" });
2071
- }
2072
- for (const e of result.captionErrors) {
2073
- emitDiagnostic({ ...e, severity: "error" });
2074
- }
2075
- for (const e of result.headingErrors) {
2076
- emitDiagnostic({ ...e, severity: "error" });
2077
- }
2078
- for (const e of result.footnoteErrors) {
2079
- emitDiagnostic({ ...e, severity: "error" });
2080
- }
2081
- for (const e of result.embedErrors) {
2082
- emitDiagnostic({ ...e, severity: "error" });
2083
- }
2084
- // Reported the way the pack build reports the very same
2085
- // finding: `file:line:column: error: message`, path first, and
2086
- // the message from the shared table, not a
2087
- // `log.error` whose timestamp prefix sat where a parser reads
2088
- // the path from, and whose text named a `reason` code rather
2089
- // than saying what to do — so one authored link produced a
2090
- // machine-readable diagnostic from one build and prose from
2091
- // another.
2092
- for (const e of result.wikiErrors) {
2093
- emitDiagnostic({
2094
- file: e.file,
2095
- ...positionOfLiteral(readRawNote(e.file), e.link, e.occurrence),
2096
- severity: "error",
2097
- // An embed's directive speaks the image vocabulary
2098
- // rather than the link one, and carries its own
2099
- // sentence; everything else is worded by the shared
2100
- // table.
2101
- message: e.message ?? linkFindingMessage(e),
2102
- });
2103
- }
2104
- // An image whose pathname the site cannot resolve, located the
2105
- // way a wikilink finding is: by searching the note for the
2106
- // literal the resolver was handed. The page is written either
2107
- // way — a missing picture is visible, and stopping before the
2108
- // write would hide every other finding in the tree behind one
2109
- // address.
2110
- for (const e of result.imageErrors) {
2111
- emitDiagnostic({
2112
- file: e.file,
2113
- ...positionOfLiteral(readRawNote(e.file), e.src, e.occurrence),
2114
- severity: "error",
2115
- message: e.message,
2116
- });
2117
- }
2118
- if (
2119
- result.tableErrors.length ||
2120
- result.expressionErrors.length ||
2121
- result.secretErrors.length ||
2122
- result.captionErrors.length ||
2123
- result.headingErrors.length ||
2124
- result.footnoteErrors.length ||
2125
- result.wikiErrors.length ||
2126
- result.imageErrors.length ||
2127
- result.embedErrors.length
2128
- ) {
2133
+ reportSiteFindings(result.written.findings);
2134
+ for (const finding of result.fonts.findings) emitDiagnostic(finding);
2135
+ if (result.fonts.findings.length) {
2129
2136
  process.exitCode = 1;
2130
2137
  return;
2131
2138
  }
2132
-
2133
- // Relation warnings do not fail a site build.
2134
- for (const f of result.mapFindings) emitDiagnostic(f);
2135
-
2136
- const s = result.stats;
2137
2139
  log.info(
2138
- `wrote ${s.homepages ?? 0} homepage(s) + ` +
2139
- `${s.content ?? 0} content page(s), ` +
2140
- `${s.maps ?? 0} with a map, to ` +
2141
- `${path.relative(process.cwd(), s.out)}`,
2140
+ `wrote ${result.written.pages.length} page(s) to ` +
2141
+ `${path.relative(process.cwd(), out)}`,
2142
2142
  );
2143
- const { file } = writeHugoConfig(config, hugo);
2144
- log.info(`wrote ${path.relative(process.cwd(), file)}`);
2145
- stageSiteFonts(config);
2143
+ if (result.fonts.count)
2144
+ log.info(
2145
+ `wrote ${result.fonts.count} script font file(s) to ` +
2146
+ `${path.relative(process.cwd(), result.fonts.dest)}`,
2147
+ );
2148
+ const { search } = await writeSiteRoot({
2149
+ pkg: config.contentPackage,
2150
+ base: siteBase(config),
2151
+ out: root,
2152
+ search: config.site.search,
2153
+ });
2154
+ if (search) {
2155
+ log.info(
2156
+ `indexed ${search.pages} page(s) for search into ` +
2157
+ `${path.relative(process.cwd(), search.dir)}`,
2158
+ );
2159
+ }
2160
+ } catch (err) {
2161
+ reportFailure(err);
2162
+ process.exitCode = 1;
2163
+ }
2164
+ },
2165
+ };
2166
+ }
2167
+
2168
+ /**
2169
+ * `package-build site serve` — serve the website, and keep it current.
2170
+ *
2171
+ * The site is built as `site build` builds it, served from the deployed root,
2172
+ * and written again as the content tree or the theme changes; every open page
2173
+ * reloads when it is. Runs until interrupted.
2174
+ *
2175
+ * @returns {object} The yargs command module.
2176
+ */
2177
+ function siteServeCommand() {
2178
+ return {
2179
+ command: "serve",
2180
+ describe: "Serve the website, and rebuild and reload it as its sources change",
2181
+ builder: (yargs) =>
2182
+ yargs
2183
+ .option("port", {
2184
+ type: "number",
2185
+ default: 1313,
2186
+ describe: "The port to listen on",
2187
+ })
2188
+ .option("host", {
2189
+ type: "string",
2190
+ default: "127.0.0.1",
2191
+ describe: "The address to listen on",
2192
+ })
2193
+ .option("out", {
2194
+ type: "string",
2195
+ describe: `The directory the site is written to (default: ${DEPLOY_ROOT})`,
2196
+ }),
2197
+ handler: async (argv) => {
2198
+ try {
2199
+ const config = argv[CONTENT_CONFIG] ?? loadPackConfig();
2200
+ const root = path.resolve(config.rootDir, argv.out ?? DEPLOY_ROOT);
2201
+ const served = await serveSite({
2202
+ config,
2203
+ root,
2204
+ out: siteDirectory(config, root),
2205
+ host: argv.host,
2206
+ port: argv.port,
2207
+ report: (event) => {
2208
+ if (event.kind === "error") reportFailure(event.error);
2209
+ else if (event.kind === "failed") {
2210
+ if (event.built) reportSiteBuild(event.built);
2211
+ for (const finding of event.findings ?? []) emitDiagnostic(finding);
2212
+ } else {
2213
+ reportSiteFindings(event.findings ?? []);
2214
+ log.info(
2215
+ event.kind === "built" ? "built the site"
2216
+ : event.kind === "theme" ? "drew every page again"
2217
+ : `wrote ${event.files.map((f) => path.relative(process.cwd(), f)).join(", ")}`,
2218
+ );
2219
+ }
2220
+ },
2221
+ });
2222
+ log.info(`serving ${served.url} — interrupt to stop`);
2223
+ await new Promise((resolve) => {
2224
+ process.once("SIGINT", resolve);
2225
+ process.once("SIGTERM", resolve);
2226
+ });
2227
+ await served.close();
2146
2228
  } catch (err) {
2147
2229
  reportFailure(err);
2148
2230
  process.exitCode = 1;
@@ -2397,7 +2479,7 @@ function transliterateCommand() {
2397
2479
  default: false,
2398
2480
  describe: "Print the code points, as `U+XXXX` separated by spaces",
2399
2481
  }),
2400
- handler: (argv) => {
2482
+ handler: async (argv) => {
2401
2483
  try {
2402
2484
  const read = mappingContext();
2403
2485
  if (!read) {
@@ -2407,7 +2489,7 @@ function transliterateCommand() {
2407
2489
  const words = (argv.text ?? []).map(String);
2408
2490
  const text =
2409
2491
  !words.length || (words.length === 1 && words[0] === "-") ?
2410
- fs.readFileSync(0, "utf8").replace(/\r?\n$/, "")
2492
+ (await readStdin()).replace(/\r?\n$/, "")
2411
2493
  : words.join(" ");
2412
2494
  const mapped = transliterate(
2413
2495
  read.ctx,
@@ -2437,7 +2519,11 @@ function transliterateCommand() {
2437
2519
 
2438
2520
  /**
2439
2521
  * `package-build lexicon <language> --map <map>` — every word of a language's
2440
- * lexicon with its native form under a map, as the build writes it.
2522
+ * lexicon with its native form under a map, as the build writes it; with
2523
+ * `--derived`, the forms its rules derive follow, each naming its word in a
2524
+ * sixth column. Under a logographic map the third column says where a
2525
+ * word's writing comes from, `own` or `parts`, or marks it `none` or
2526
+ * `ambiguous`.
2441
2527
  *
2442
2528
  * @returns {object} The yargs command module.
2443
2529
  */
@@ -2461,6 +2547,12 @@ function lexiconCommand() {
2461
2547
  type: "string",
2462
2548
  describe: "The face; required when the map lists more than one",
2463
2549
  })
2550
+ .option("derived", {
2551
+ type: "boolean",
2552
+ default: false,
2553
+ describe:
2554
+ "Also list the forms the language's rules derive, each beside its lexicon word",
2555
+ })
2464
2556
  .option("json", {
2465
2557
  type: "boolean",
2466
2558
  default: false,
@@ -2477,6 +2569,7 @@ function lexiconCommand() {
2477
2569
  lang: String(argv.language),
2478
2570
  map: String(argv.map),
2479
2571
  font: argv.font,
2572
+ derived: Boolean(argv.derived),
2480
2573
  });
2481
2574
  if ("problem" in listed) {
2482
2575
  reportMapping(read.config, listed);
@@ -2489,9 +2582,15 @@ function lexiconCommand() {
2489
2582
  [
2490
2583
  entry.form,
2491
2584
  entry.native,
2492
- entry.exception ? "exception" : "rows",
2585
+ entry.source === undefined ?
2586
+ entry.exception ?
2587
+ "exception"
2588
+ : "rows"
2589
+ : entry.ambiguous ? "ambiguous"
2590
+ : entry.source,
2493
2591
  entry.class ?? "",
2494
2592
  entry.gloss ?? "",
2593
+ ...(argv.derived ? [entry.from ?? ""] : []),
2495
2594
  ].join("\t"),
2496
2595
  );
2497
2596
  } catch (err) {
@@ -2502,6 +2601,64 @@ function lexiconCommand() {
2502
2601
  };
2503
2602
  }
2504
2603
 
2604
+ /**
2605
+ * `package-build core [--language <name>] [--json]` — the concepts of the core
2606
+ * vocabulary each covering language has no word for, by group.
2607
+ *
2608
+ * @returns {object} The yargs command module.
2609
+ */
2610
+ // eslint-disable-next-line
2611
+ function coreCommand() {
2612
+ return {
2613
+ command: "core",
2614
+ describe: "List the core concepts each covering language has no word for",
2615
+ builder: (yargs) =>
2616
+ yargs
2617
+ .option("language", {
2618
+ type: "string",
2619
+ describe:
2620
+ "Report this language alone, by its name, shortcode or reference, whether or not it states `data.core`",
2621
+ })
2622
+ .option("json", {
2623
+ type: "boolean",
2624
+ default: false,
2625
+ describe: "Print the report as JSON",
2626
+ }),
2627
+ handler: (argv) => {
2628
+ try {
2629
+ const config = loadPackConfig();
2630
+ const problems = [];
2631
+ const records = indexRecordsFor({
2632
+ contentBase: config.paths.content,
2633
+ config,
2634
+ skipDirectories: config.skipDirectories,
2635
+ problems,
2636
+ });
2637
+ for (const problem of problems) emitDiagnostic(problem);
2638
+ if (problems.length) {
2639
+ process.exitCode = 1;
2640
+ return;
2641
+ }
2642
+ const report = coreReport(records, { language: argv.language });
2643
+ if ("problem" in report) {
2644
+ emitDiagnostic({
2645
+ file: config.paths.content,
2646
+ severity: "error",
2647
+ message: report.problem,
2648
+ });
2649
+ process.exitCode = 1;
2650
+ return;
2651
+ }
2652
+ if (argv.json) console.log(JSON.stringify(report, null, 2));
2653
+ else process.stdout.write(coreReportText(report));
2654
+ } catch (err) {
2655
+ reportFailure(err);
2656
+ process.exitCode = 1;
2657
+ }
2658
+ },
2659
+ };
2660
+ }
2661
+
2505
2662
  /**
2506
2663
  * `package-build reachability <dir> [file]` — check that a corpus reads through.
2507
2664
  *