@heroiclands/package-build 23.13.0 → 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 (233) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -1
  3. package/assets/preview/gitea.ini +12 -7
  4. package/assets/styles/content-base.css +150 -1
  5. package/bin/content-commands.mjs +356 -210
  6. package/bin/package-build.mjs +3 -2
  7. package/ci/check-banners.mjs +7 -7
  8. package/config.mjs +21 -6
  9. package/content-config.mjs +199 -80
  10. package/docs/api.md +93 -72
  11. package/docs/authoring/links-and-markup.md +316 -78
  12. package/docs/commands.md +244 -78
  13. package/docs/configuration.md +210 -102
  14. package/docs/content-format.md +2 -2
  15. package/docs/diagnostics.md +2 -2
  16. package/docs/getting-started.md +1 -1
  17. package/docs/guides/icons.md +1 -1
  18. package/docs/guides/module-project.md +5 -8
  19. package/docs/guides/release-and-deploy.md +2 -2
  20. package/docs/guides/site.md +112 -23
  21. package/docs/guides/system-project.md +1 -1
  22. package/docs/guides/writing-in-invented-scripts.md +1247 -0
  23. package/docs/index.md +1 -0
  24. package/docs/project-setup.md +28 -30
  25. package/docs/reference/format-details.md +589 -308
  26. package/docs/reference/note-types.md +40 -10
  27. package/docs/reference/theme-pages.md +127 -108
  28. package/docs/source-preview.md +15 -1
  29. package/engine/asset-shipping.mjs +22 -10
  30. package/engine/base-compiler.mjs +62 -104
  31. package/engine/changelog-lint.mjs +1 -1
  32. package/engine/content-address.mjs +1 -1
  33. package/engine/content-alerts.mjs +8 -4
  34. package/engine/content-blocks.mjs +26 -11
  35. package/engine/content-embeds.mjs +1 -1
  36. package/engine/content-expand.mjs +376 -0
  37. package/engine/content-figures.mjs +13 -6
  38. package/engine/content-format.yaml +8 -0
  39. package/engine/content-glyphs.mjs +213 -0
  40. package/engine/content-images.mjs +6 -7
  41. package/engine/content-links.mjs +3 -3
  42. package/engine/content-lint.mjs +1 -1
  43. package/engine/content-render.mjs +453 -0
  44. package/engine/content-scripts.mjs +326 -27
  45. package/engine/content-spans.mjs +4 -1
  46. package/engine/content-styles.mjs +91 -0
  47. package/engine/content-symbols.mjs +186 -0
  48. package/engine/content-verse.mjs +6 -18
  49. package/engine/core-report.mjs +169 -0
  50. package/engine/core-vocabulary.mjs +208 -0
  51. package/engine/dictionary-html.mjs +119 -0
  52. package/engine/field-reference.mjs +1 -1
  53. package/engine/frontmatter-lint.mjs +1 -1
  54. package/engine/heading-attributes.mjs +89 -0
  55. package/engine/helpers.mjs +61 -101
  56. package/engine/homepage.mjs +17 -29
  57. package/engine/html-minify.mjs +70 -0
  58. package/engine/index.mjs +8 -5
  59. package/engine/language-rows.mjs +8 -2
  60. package/engine/language-terms.mjs +115 -14
  61. package/engine/language-words.mjs +12 -0
  62. package/engine/logographic.mjs +255 -0
  63. package/engine/map-lexicon.mjs +221 -25
  64. package/engine/map-places.mjs +5 -4
  65. package/engine/markdown-expressions.mjs +0 -2
  66. package/engine/markdownlint-run.mjs +9 -3
  67. package/engine/markup-validation.mjs +21 -0
  68. package/engine/metre-declaration.mjs +134 -0
  69. package/engine/note-matter.mjs +2 -2
  70. package/engine/note-vocabulary.mjs +187 -3
  71. package/engine/pdf-build.mjs +68 -133
  72. package/engine/pdf-render.mjs +102 -44
  73. package/engine/post-fields.mjs +157 -0
  74. package/engine/preview-index.mjs +130 -0
  75. package/engine/preview-source.mjs +450 -0
  76. package/engine/project-init.mjs +5 -12
  77. package/engine/prose-config.mjs +3 -3
  78. package/engine/run-words.mjs +3 -2
  79. package/engine/script-emphasis-terms.mjs +38 -0
  80. package/engine/script-emphasis.mjs +295 -0
  81. package/engine/script-font-delivery.mjs +4 -4
  82. package/engine/site-build.mjs +259 -314
  83. package/engine/site-config.mjs +26 -238
  84. package/engine/site-files.mjs +175 -0
  85. package/engine/site-index.mjs +9 -4
  86. package/engine/site-preview.mjs +50 -523
  87. package/engine/site-render.mjs +593 -0
  88. package/engine/site-root.mjs +22 -13
  89. package/engine/site-search.mjs +161 -0
  90. package/engine/site-serve.mjs +273 -0
  91. package/engine/site-theme-banner.mjs +154 -0
  92. package/engine/site-theme-urls.mjs +125 -0
  93. package/engine/site-theme.mjs +926 -0
  94. package/engine/text-maps.mjs +57 -10
  95. package/engine/typographer.mjs +248 -0
  96. package/engine/verse-metre.mjs +357 -0
  97. package/engine/wikilink-syntax.mjs +29 -47
  98. package/engine/wikilinks.mjs +758 -279
  99. package/engine/word-forms.mjs +39 -24
  100. package/engine/word-rows.mjs +12 -2
  101. package/engine/word-terms.mjs +29 -4
  102. package/manifest.mjs +3 -4
  103. package/package.json +10 -7
  104. package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
  105. package/site-theme/layouts/base.hbs +31 -0
  106. package/site-theme/layouts/blog-list.hbs +30 -0
  107. package/site-theme/layouts/blog-post.hbs +40 -0
  108. package/site-theme/layouts/home.hbs +35 -0
  109. package/site-theme/layouts/list.hbs +20 -0
  110. package/site-theme/layouts/not-found.hbs +31 -0
  111. package/site-theme/layouts/page.hbs +7 -0
  112. package/site-theme/partials/copy-buttons.hbs +37 -0
  113. package/site-theme/partials/draft-notice.hbs +5 -0
  114. package/site-theme/partials/exturl.hbs +2 -0
  115. package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
  116. package/site-theme/partials/header.hbs +44 -0
  117. package/site-theme/partials/hero-banner.hbs +11 -0
  118. package/site-theme/partials/infobox-entry.hbs +2 -0
  119. package/site-theme/partials/infobox-section.hbs +31 -0
  120. package/site-theme/partials/infobox-value.hbs +2 -0
  121. package/site-theme/partials/infobox.hbs +18 -0
  122. package/site-theme/partials/list-members.hbs +9 -0
  123. package/site-theme/partials/nav-links.hbs +5 -0
  124. package/site-theme/partials/page-body.hbs +48 -0
  125. package/site-theme/partials/page-list.hbs +14 -0
  126. package/site-theme/partials/related.hbs +24 -0
  127. package/site-theme/partials/search-meta.hbs +6 -0
  128. package/site-theme/partials/search-names.hbs +2 -0
  129. package/site-theme/partials/search.hbs +52 -0
  130. package/site-theme/partials/tags.hbs +4 -0
  131. package/site-theme/partials/toc.hbs +7 -0
  132. package/{hugo-theme → site-theme}/static/css/style.css +14 -0
  133. package/stage.mjs +1 -2
  134. package/types/config.d.mts +8 -4
  135. package/types/content-config.d.mts +15 -15
  136. package/types/engine/asset-shipping.d.mts +5 -2
  137. package/types/engine/base-compiler.d.mts +20 -2
  138. package/types/engine/content-address.d.mts +1 -1
  139. package/types/engine/content-alerts.d.mts +8 -2
  140. package/types/engine/content-blocks.d.mts +11 -4
  141. package/types/engine/content-expand.d.mts +288 -0
  142. package/types/engine/content-figures.d.mts +11 -3
  143. package/types/engine/content-glyphs.d.mts +85 -0
  144. package/types/engine/content-images.d.mts +6 -7
  145. package/types/engine/content-links.d.mts +1 -1
  146. package/types/engine/content-render.d.mts +199 -0
  147. package/types/engine/content-scripts.d.mts +62 -0
  148. package/types/engine/content-spans.d.mts +1 -1
  149. package/types/engine/content-styles.d.mts +33 -0
  150. package/types/engine/content-symbols.d.mts +35 -0
  151. package/types/engine/core-report.d.mts +45 -0
  152. package/types/engine/core-vocabulary.d.mts +53 -0
  153. package/types/engine/dictionary-html.d.mts +8 -0
  154. package/types/engine/field-reference.d.mts +1 -1
  155. package/types/engine/heading-attributes.d.mts +20 -0
  156. package/types/engine/helpers.d.mts +3 -10
  157. package/types/engine/homepage.d.mts +11 -24
  158. package/types/engine/html-minify.d.mts +9 -0
  159. package/types/engine/index.d.mts +2 -1
  160. package/types/engine/language-terms.d.mts +24 -2
  161. package/types/engine/logographic.d.mts +45 -0
  162. package/types/engine/map-lexicon.d.mts +48 -8
  163. package/types/engine/map-places.d.mts +8 -0
  164. package/types/engine/markdown-expressions.d.mts +0 -2
  165. package/types/engine/metre-declaration.d.mts +39 -0
  166. package/types/engine/pdf-render.d.mts +6 -2
  167. package/types/engine/post-fields.d.mts +44 -0
  168. package/types/engine/preview-index.d.mts +72 -0
  169. package/types/engine/preview-source.d.mts +51 -0
  170. package/types/engine/script-emphasis-terms.d.mts +21 -0
  171. package/types/engine/script-emphasis.d.mts +84 -0
  172. package/types/engine/script-font-delivery.d.mts +4 -4
  173. package/types/engine/site-build.d.mts +71 -113
  174. package/types/engine/site-config.d.mts +14 -134
  175. package/types/engine/site-files.d.mts +73 -0
  176. package/types/engine/site-index.d.mts +11 -3
  177. package/types/engine/site-preview.d.mts +15 -19
  178. package/types/engine/site-render.d.mts +171 -0
  179. package/types/engine/site-root.d.mts +12 -4
  180. package/types/engine/site-search.d.mts +54 -0
  181. package/types/engine/site-serve.d.mts +44 -0
  182. package/types/engine/site-theme-banner.d.mts +51 -0
  183. package/types/engine/site-theme-urls.d.mts +60 -0
  184. package/types/engine/site-theme.d.mts +127 -0
  185. package/types/engine/text-maps.d.mts +17 -3
  186. package/types/engine/typographer.d.mts +11 -0
  187. package/types/engine/verse-metre.d.mts +131 -0
  188. package/types/engine/wikilink-syntax.d.mts +30 -48
  189. package/types/engine/wikilinks.d.mts +330 -14
  190. package/types/engine/word-forms.d.mts +3 -2
  191. package/types/manifest.d.mts +3 -4
  192. package/types/stage.d.mts +1 -2
  193. package/types/vendor/markdownlint.d.mts +8 -0
  194. package/vendor/markdownlint.mjs +20388 -0
  195. package/engine/web-wikilinks.mjs +0 -594
  196. package/hugo-theme/layouts/404.html +0 -85
  197. package/hugo-theme/layouts/_default/baseof.html +0 -67
  198. package/hugo-theme/layouts/_default/list.html +0 -139
  199. package/hugo-theme/layouts/_default/single.html +0 -4
  200. package/hugo-theme/layouts/blog/list.html +0 -54
  201. package/hugo-theme/layouts/blog-post/single.html +0 -36
  202. package/hugo-theme/layouts/index.html +0 -75
  203. package/hugo-theme/layouts/partials/banner-url.html +0 -141
  204. package/hugo-theme/layouts/partials/cdn-url.html +0 -36
  205. package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
  206. package/hugo-theme/layouts/partials/draft-notice.html +0 -41
  207. package/hugo-theme/layouts/partials/draft-state.html +0 -26
  208. package/hugo-theme/layouts/partials/header.html +0 -35
  209. package/hugo-theme/layouts/partials/hero-banner.html +0 -85
  210. package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
  211. package/hugo-theme/layouts/partials/infobox/items.html +0 -26
  212. package/hugo-theme/layouts/partials/infobox/section.html +0 -56
  213. package/hugo-theme/layouts/partials/infobox/value.html +0 -43
  214. package/hugo-theme/layouts/partials/infobox.html +0 -57
  215. package/hugo-theme/layouts/partials/page-body.html +0 -152
  216. package/hugo-theme/layouts/partials/related.html +0 -138
  217. package/hugo-theme/layouts/partials/search.html +0 -84
  218. package/hugo-theme/layouts/partials/site-url.html +0 -32
  219. package/hugo-theme/layouts/partials/tags.html +0 -30
  220. package/hugo-theme/layouts/partials/toc.html +0 -28
  221. package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
  222. package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
  223. package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
  224. package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
  225. package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
  226. package/hugo-theme/theme.toml +0 -11
  227. package/types/engine/web-wikilinks.d.mts +0 -107
  228. /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
  229. /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
  230. /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
  231. /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
  232. /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
  233. /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
@@ -47,7 +47,7 @@ import log from "loglevel";
47
47
  import prefix from "loglevel-plugin-prefix";
48
48
  import { readStdin } from "../engine/read-stdin.mjs";
49
49
  import { compilePacks, cleanPacks, unpackPacks } from "../engine/compendiums.mjs";
50
- import { compilesFoundryDocuments, publishesContentPages } from "../content-config.mjs";
50
+ import { compilesFoundryDocuments, publishesContentPages, siteBase } from "../content-config.mjs";
51
51
  import { loadPackConfig, packConfigPath } from "../engine/pack-config.mjs";
52
52
  import { buildPdf, listBookAssets } from "../engine/pdf-build.mjs";
53
53
  import { summaryLines } from "../engine/pdf-coverage.mjs";
@@ -65,19 +65,7 @@ import {
65
65
  unaddressableForeignPackages,
66
66
  formatUnaddressableFinding,
67
67
  } from "../engine/metadata-index.mjs";
68
- import {
69
- fetchNavigation,
70
- generateHugoConfig,
71
- HUGO_SOURCE,
72
- writeHugoConfig,
73
- } from "../engine/site-config.mjs";
74
- import {
75
- checkScriptFonts,
76
- copyScriptFonts,
77
- scriptFontFiles,
78
- SITE_FONTS_DIR,
79
- siteFontsDestination,
80
- } from "../engine/script-font-delivery.mjs";
68
+ import { DEPLOY_ROOT, fetchNavigation } from "../engine/site-config.mjs";
81
69
  import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
82
70
  import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
83
71
  import { checkArtSlotSizes } from "../engine/art-fields.mjs";
@@ -118,6 +106,8 @@ import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
118
106
  import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
119
107
  import { markupFindings } from "../engine/markup-validation.mjs";
120
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";
121
111
  import {
122
112
  checkLexiconNatives,
123
113
  checkMapFonts,
@@ -137,8 +127,12 @@ import {
137
127
  import {
138
128
  buildSite,
139
129
  gatesFailed,
130
+ siteBuildFailed,
140
131
  formatUnaddressableFinding as formatUnaddressable,
141
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";
142
136
  import { auditLinks, buildLinkIndex, walkReachability } from "../engine/content-links.mjs";
143
137
  import { prepareLinkSqlTables, prepareTreeSqlTables } from "../engine/sql-tables.mjs";
144
138
  import { createContentSession, CONTENT_TARGETS } from "../engine/content-session.mjs";
@@ -325,6 +319,7 @@ export function registerContentCommands(cli) {
325
319
  .command(withIndexPreflight(reachabilityCommand()))
326
320
  .command(transliterateCommand())
327
321
  .command(lexiconCommand())
322
+ .command(coreCommand())
328
323
  .command(addressesCommand())
329
324
  .command(withIndexPreflight(npcCommand()));
330
325
  }
@@ -1293,6 +1288,7 @@ function lintCommand() {
1293
1288
  // Each mapping note's faces, each language's native forms
1294
1289
  // and each language's `note`, against the faces and maps.
1295
1290
  ...index.notes.flatMap((note) => checkMapFonts(note, runs)),
1291
+ ...index.notes.flatMap((note) => checkScriptNoteFonts(note, runs)),
1296
1292
  ...index.notes.flatMap((note) => checkLexiconNatives(note, runs)),
1297
1293
  ...languageNoteFindings({ ...config, configFile: packConfigPath() }, records),
1298
1294
  ...index.notes.flatMap(firstUseChecker),
@@ -1926,60 +1922,123 @@ function pdfCommand() {
1926
1922
  }
1927
1923
 
1928
1924
  /**
1929
- * Copy the installed script fonts into the Hugo static tree, under a path that
1930
- * names their version, and drop any other version's copy.
1931
- *
1932
- * A package that has not installed `@heroiclands/fonts`, or sets no text in
1933
- * one of its faces, gets no copy; a package missing a file its fonts depend on
1934
- * gets a located error and a non-zero exit.
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.
1935
1928
  *
1936
- * @param {object} config - The resolved content configuration.
1937
- * @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.
1938
1932
  */
1939
- function stageSiteFonts(config) {
1940
- const root = path.join(config.rootDir, HUGO_SOURCE, "static", SITE_FONTS_DIR);
1941
- fs.rmSync(root, { recursive: true, force: true });
1942
- const found = scriptFontFiles(config, ["woff2"]);
1943
- if (!found) return;
1944
- const findings = checkScriptFonts(config);
1945
- for (const finding of findings) emitDiagnostic(finding);
1946
- if (findings.length) {
1947
- process.exitCode = 1;
1948
- 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
+ });
1949
1950
  }
1950
- const dest = path.join(
1951
- config.rootDir,
1952
- HUGO_SOURCE,
1953
- "static",
1954
- siteFontsDestination(found.version),
1955
- );
1956
- const count = copyScriptFonts(config, dest, ["woff2"]);
1957
- 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;
1958
2034
  }
1959
2035
 
1960
2036
  /**
1961
- * `package-build site` — publish the content tree as a website.
2037
+ * `package-build site` — the website commands.
1962
2038
  *
1963
2039
  * The sibling of `package compile`: the same tree, rendered as pages instead of
1964
- * compiled into packs. Everything a consumer would otherwise write for itself —
1965
- * the walk, the address derivation, the address index, table expansion,
1966
- * wikilink resolution, code-fence protection and the foreign-manifest merge —
1967
- * happens here, from configuration. So does the Hugo configuration: the whole
1968
- * source tree Hugo reads lands under `build/hugo/`, and the consumer's script
1969
- * runs Hugo over it. With `site.maps` on, every place
1970
- * page with a border or a route carries the map from that place.
1971
- *
1972
- * **The Hugo configuration is generated before anything is written.** Its
1973
- * sources — `package.json`'s `homepage`, the cached navigation, the installed
1974
- * theme — are each a way the build can fail, and failing before the output
1975
- * tree is cleared leaves the last good site in place to be looked at. Nothing
1976
- * the walk reads changes it: a site renders its homepage and its pages, and
1977
- * the kinds Hugo disables are the same on every site.
1978
- *
1979
- * **Each gate is reported and the run stops at the first that fires.** They are
1980
- * ordered so the report names the cause rather than its symptoms: an unusable
1981
- * manifest, reported after the links that failed because of it, reads as a pile
1982
- * 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.
1983
2042
  *
1984
2043
  * @returns {object} The yargs command module.
1985
2044
  */
@@ -1987,163 +2046,185 @@ function stageSiteFonts(config) {
1987
2046
  function siteCommand() {
1988
2047
  return {
1989
2048
  command: "site",
1990
- describe: "Build the Hugo source tree from the content tree",
1991
- builder: () => {},
1992
- 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) => {
1993
2121
  try {
1994
- const config = loadPackConfig();
1995
- // Generated before the walk, to fail fast on a missing or
1996
- // mismatched source — `homepage`, the manifest title, the
1997
- // cached navigation, the installed theme — while the last
1998
- // good site is still in place to be looked at. Written only
1999
- // once the walk has succeeded.
2000
- const hugo = generateHugoConfig(config);
2001
- const result = buildSite({
2002
- config,
2003
- sqlTables: await prepareTreeSqlTables(config.paths.content, {
2004
- skipDirectories: config.skipDirectories,
2005
- config,
2006
- audience: "public",
2007
- }),
2008
- });
2009
- const { gates } = result;
2010
-
2011
- // First, because it is decided before the tree is walked and
2012
- // before the output is cleared: a package with no front page,
2013
- // or two competing for it, has nothing to say about its pages
2014
- // yet.
2015
- for (const f of gates.homepages) emitDiagnostic(f);
2016
- for (const f of gates.frontmatterLinks) {
2017
- emitDiagnostic({
2018
- file: f.file,
2019
- severity: "error",
2020
- message:
2021
- `wikilink ${f.link} authored in frontmatter at ` +
2022
- `${f.path} — frontmatter is data, is copied to the ` +
2023
- `page verbatim, and reaches the reader as brackets`,
2024
- });
2025
- }
2026
- for (const f of gates.addressErrors) {
2027
- emitDiagnostic({
2028
- file: f.file,
2029
- severity: "error",
2030
- message: `cannot derive a URL: ${f.reason}`,
2031
- });
2032
- }
2033
- for (const s of gates.staleManifests) {
2034
- emitDiagnostic({
2035
- file: cachedIndexPath(loadPackConfig(), s.package),
2036
- severity: "error",
2037
- message: `unusable content index: ${s.reason}`,
2038
- });
2039
- }
2040
- for (const f of gates.unaddressable) {
2041
- console.error(formatUnaddressable(f, loadPackConfig()));
2042
- }
2043
- 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) {
2044
2130
  process.exitCode = 1;
2045
2131
  return;
2046
2132
  }
2047
-
2048
- // Reported after the write rather than before it: both are
2049
- // failures of individual notes, and stopping the whole build
2050
- // before anything is emitted would make a single bad table
2051
- // hide every other problem in the tree.
2052
- // `file:line:column: error: message`, path first — the same
2053
- // shape the pack build and the wikilink findings below use. It
2054
- // was prose with a timestamp where a parser reads the path,
2055
- // which made one authored table produce a machine-readable
2056
- // diagnostic from one build and something ungreppable from the
2057
- // other.
2058
- for (const e of result.tableErrors) {
2059
- emitDiagnostic({
2060
- file: e.source,
2061
- line: typeof e.line === "number" ? e.line + 1 : undefined,
2062
- column: e.column,
2063
- severity: "error",
2064
- message: e.reason,
2065
- });
2066
- }
2067
- for (const e of result.expressionErrors) {
2068
- emitDiagnostic(e);
2069
- }
2070
- for (const e of result.secretErrors) {
2071
- emitDiagnostic({ ...e, severity: "error" });
2072
- }
2073
- for (const e of result.captionErrors) {
2074
- emitDiagnostic({ ...e, severity: "error" });
2075
- }
2076
- for (const e of result.headingErrors) {
2077
- emitDiagnostic({ ...e, severity: "error" });
2078
- }
2079
- for (const e of result.footnoteErrors) {
2080
- emitDiagnostic({ ...e, severity: "error" });
2081
- }
2082
- for (const e of result.embedErrors) {
2083
- emitDiagnostic({ ...e, severity: "error" });
2084
- }
2085
- // Reported the way the pack build reports the very same
2086
- // finding: `file:line:column: error: message`, path first, and
2087
- // the message from the shared table, not a
2088
- // `log.error` whose timestamp prefix sat where a parser reads
2089
- // the path from, and whose text named a `reason` code rather
2090
- // than saying what to do — so one authored link produced a
2091
- // machine-readable diagnostic from one build and prose from
2092
- // another.
2093
- for (const e of result.wikiErrors) {
2094
- emitDiagnostic({
2095
- file: e.file,
2096
- ...positionOfLiteral(readRawNote(e.file), e.link, e.occurrence),
2097
- severity: "error",
2098
- // An embed's directive speaks the image vocabulary
2099
- // rather than the link one, and carries its own
2100
- // sentence; everything else is worded by the shared
2101
- // table.
2102
- message: e.message ?? linkFindingMessage(e),
2103
- });
2104
- }
2105
- // An image whose pathname the site cannot resolve, located the
2106
- // way a wikilink finding is: by searching the note for the
2107
- // literal the resolver was handed. The page is written either
2108
- // way — a missing picture is visible, and stopping before the
2109
- // write would hide every other finding in the tree behind one
2110
- // address.
2111
- for (const e of result.imageErrors) {
2112
- emitDiagnostic({
2113
- file: e.file,
2114
- ...positionOfLiteral(readRawNote(e.file), e.src, e.occurrence),
2115
- severity: "error",
2116
- message: e.message,
2117
- });
2118
- }
2119
- if (
2120
- result.tableErrors.length ||
2121
- result.expressionErrors.length ||
2122
- result.secretErrors.length ||
2123
- result.captionErrors.length ||
2124
- result.headingErrors.length ||
2125
- result.footnoteErrors.length ||
2126
- result.wikiErrors.length ||
2127
- result.imageErrors.length ||
2128
- result.embedErrors.length
2129
- ) {
2133
+ reportSiteFindings(result.written.findings);
2134
+ for (const finding of result.fonts.findings) emitDiagnostic(finding);
2135
+ if (result.fonts.findings.length) {
2130
2136
  process.exitCode = 1;
2131
2137
  return;
2132
2138
  }
2133
-
2134
- // Relation warnings do not fail a site build.
2135
- for (const f of result.mapFindings) emitDiagnostic(f);
2136
-
2137
- const s = result.stats;
2138
2139
  log.info(
2139
- `wrote ${s.homepages ?? 0} homepage(s) + ` +
2140
- `${s.content ?? 0} content page(s), ` +
2141
- `${s.maps ?? 0} with a map, to ` +
2142
- `${path.relative(process.cwd(), s.out)}`,
2140
+ `wrote ${result.written.pages.length} page(s) to ` +
2141
+ `${path.relative(process.cwd(), out)}`,
2143
2142
  );
2144
- const { file } = writeHugoConfig(config, hugo);
2145
- log.info(`wrote ${path.relative(process.cwd(), file)}`);
2146
- 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();
2147
2228
  } catch (err) {
2148
2229
  reportFailure(err);
2149
2230
  process.exitCode = 1;
@@ -2440,7 +2521,9 @@ function transliterateCommand() {
2440
2521
  * `package-build lexicon <language> --map <map>` — every word of a language's
2441
2522
  * lexicon with its native form under a map, as the build writes it; with
2442
2523
  * `--derived`, the forms its rules derive follow, each naming its word in a
2443
- * sixth column.
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`.
2444
2527
  *
2445
2528
  * @returns {object} The yargs command module.
2446
2529
  */
@@ -2499,7 +2582,12 @@ function lexiconCommand() {
2499
2582
  [
2500
2583
  entry.form,
2501
2584
  entry.native,
2502
- entry.exception ? "exception" : "rows",
2585
+ entry.source === undefined ?
2586
+ entry.exception ?
2587
+ "exception"
2588
+ : "rows"
2589
+ : entry.ambiguous ? "ambiguous"
2590
+ : entry.source,
2503
2591
  entry.class ?? "",
2504
2592
  entry.gloss ?? "",
2505
2593
  ...(argv.derived ? [entry.from ?? ""] : []),
@@ -2513,6 +2601,64 @@ function lexiconCommand() {
2513
2601
  };
2514
2602
  }
2515
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
+
2516
2662
  /**
2517
2663
  * `package-build reachability <dir> [file]` — check that a corpus reads through.
2518
2664
  *