@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
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /*
3
2
  * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
4
3
  * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
@@ -13,43 +12,26 @@
13
12
  */
14
13
 
15
14
  /**
16
- * The `content-build` command line — compile / unpack / clean LevelDB packs.
17
- *
18
- * A thin `yargs` front end over `../engine/compendiums.mjs`. **Every side
19
- * effect the pack pipeline has lives here**: argv parsing, `loglevel`
20
- * configuration, directory creation, reading the shipped Foundry package
21
- * manifest, and the process exit code. The library itself is import-safe, so a
22
- * consuming repository's build — or a test — can call it without any of this
23
- * happening.
24
- *
25
- * The side effects that need *configuration* live inside the command handler,
26
- * not at module scope, so `--version` and `--help` answer in a directory that
27
- * has neither a `package-build.config.yaml` nor a package manifest.
28
- * Running an actual command still resolves both, and still fails loudly when
29
- * either is missing.
30
- *
31
- * Every path and pack name it hands the library comes from the consuming
32
- * repository's `package-build.config.yaml`, located by
33
- * `engine/pack-config.mjs`; nothing about any one repository's layout is
34
- * written here.
15
+ * Content command modules registered by the `package-build` CLI.
16
+ * Configuration is read in handlers, so help and version work without a
17
+ * package configuration. Build inputs come from `package-build.config.yaml`.
35
18
  *
36
19
  * Usage:
37
- * npx content-build package compile [pack]
38
- * npx content-build package unpack [pack] [entry]
39
- * npx content-build package clean [pack] [entry]
40
- * npx content-build docs item-fields [--out <path>] [--title <title>]
41
- * npx content-build lint [root] [--no-references]
42
- * npx content-build content-format schema --schema <system>=<path>
43
- * npx content-build content-format fields [--fields <system>]
44
- * npx content-build content-format notes [root] [--strict]
45
- * npx content-build links [root] [--manifests <dir>]
46
- * npx content-build format [paths..] [--write]
47
- * npx content-build markdown [paths..] [--fix]
48
- * npx content-build manifest [root] [--out <dir>]
49
- * npx content-build site [--out <dir>]
50
- * npx content-build map [--tree] [--from <shortcode>] [--chart <shortcode>] [--travel] [--out <dir>]
51
- * npx content-build reachability <dir> [file] [--index <shortcode>]
52
- * npx content-build addresses diff --from <zip|dir> [--strict]
20
+ * npx package-build package compile [pack]
21
+ * npx package-build package unpack [pack] [entry]
22
+ * npx package-build package clean [pack] [entry]
23
+ * npx package-build docs item-fields [--out <path>] [--title <title>]
24
+ * npx package-build lint [root] [--no-references]
25
+ * npx package-build content-format schema --schema <system>=<path>
26
+ * npx package-build content-format fields [--fields <system>]
27
+ * npx package-build content-format notes [root] [--strict]
28
+ * npx package-build links [root] [--manifests <dir>]
29
+ * npx package-build format [paths..] [--write]
30
+ * npx package-build markdown [paths..] [--fix]
31
+ * npx package-build site [--out <dir>]
32
+ * npx package-build map [--tree] [--from <shortcode>] [--chart <shortcode>] [--travel] [--out <dir>]
33
+ * npx package-build reachability <dir> [file] [--index <shortcode>]
34
+ * npx package-build addresses diff --from <zip|dir> [--strict]
53
35
  *
54
36
  * In a consuming repository, wrapped as npm scripts — SoHL spells them:
55
37
  * npm run build:compiledb // → … package compile (all packs)
@@ -61,11 +43,9 @@ import fs from "fs";
61
43
  import path from "node:path";
62
44
  import log from "loglevel";
63
45
  import prefix from "loglevel-plugin-prefix";
64
- import yargs from "yargs";
65
- import { hideBin } from "yargs/helpers";
66
46
  import { compilePacks, cleanPacks, unpackPacks } from "../engine/compendiums.mjs";
67
- import { compilesFoundryDocuments } from "../content-config.mjs";
68
- import { loadPackConfig } from "../engine/pack-config.mjs";
47
+ import { compilesFoundryDocuments, publishesContentPages } from "../content-config.mjs";
48
+ import { loadPackConfig, packConfigPath } from "../engine/pack-config.mjs";
69
49
  import { buildPdf } from "../engine/pdf-build.mjs";
70
50
  import {
71
51
  fetchAllCatalogs,
@@ -76,12 +56,14 @@ import {
76
56
  } from "../engine/foreign-catalog.mjs";
77
57
  import {
78
58
  metadataRelationships,
59
+ cachedMetadataIndexes,
79
60
  cachedIndexPath,
80
61
  unaddressableForeignPackages,
81
62
  formatUnaddressableFinding,
82
63
  } from "../engine/metadata-index.mjs";
83
64
  import { fetchNavigation, generateHugoConfig, writeHugoConfig } from "../engine/site-config.mjs";
84
65
  import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
66
+ import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
85
67
  import { lintContentTree } from "../engine/content-lint.mjs";
86
68
  import { lintNoteStates } from "../engine/stub-lint.mjs";
87
69
  import { lintContentCharset } from "../engine/content-charset.mjs";
@@ -115,6 +97,8 @@ import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
115
97
  import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
116
98
  import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
117
99
  import { checkFormatting, checkPrettierConventions, lintMarkdown } from "../engine/prose-lint.mjs";
100
+ import { lintProse } from "../engine/readability-lint.mjs";
101
+ import { loadPackageBuildConfig } from "../config.mjs";
118
102
  import {
119
103
  authoredFrontmatter,
120
104
  emitContentIndex,
@@ -170,21 +154,6 @@ function configuredPacks() {
170
154
  return loadPackConfig().packDirectories.map((name) => ({ name }));
171
155
  }
172
156
 
173
- /**
174
- * This package's own version, for `--version`.
175
- *
176
- * Read from the package's `package.json` rather than left to yargs, which
177
- * defaults to the *nearest* `package.json` walking up from the working
178
- * directory — inside a consuming repository that is the consumer's manifest, so
179
- * `content-build --version` reported the consumer's version instead of the
180
- * toolchain's.
181
- *
182
- * @returns {string} The `version` field of this package's manifest.
183
- */
184
- function ownVersion() {
185
- return JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
186
- }
187
-
188
157
  // Configure loglevel
189
158
  log.setLevel("info"); // Set desired logging level
190
159
 
@@ -246,33 +215,109 @@ function readRawNote(file) {
246
215
  */
247
216
  const SHIPPED_ITEM_FIELDS = { sohl: ITEM_FIELDS, hm3: HM3_ITEM_FIELDS };
248
217
 
249
- const argv = yargs(hideBin(process.argv))
250
- .command(packageCommand())
251
- .command(depsCommand())
252
- .command(docsCommand())
253
- .command(lintCommand())
254
- .command(contentFormatCommand())
255
- .command(linksCommand())
256
- .command(formatCommand())
257
- .command(markdownCommand())
258
- .command(contentIndexCommand())
259
- .command(siteCommand())
260
- .command(pdfCommand())
261
- .command(mapCommand())
262
- .command(reachabilityCommand())
263
- .command(addressesCommand())
264
- .version(ownVersion())
265
- .help()
266
- .alias("help", "h")
267
- // Every invocation this CLI accepts must be one it performs. yargs
268
- // gives neither guarantee by default: without `demandCommand` a bare
269
- // `content-build` exits 0 in silence, and without `strict` an unknown
270
- // command or option is ignored rather than reported. Both would read as
271
- // success from a `run-s` chain, so a typo in a build script passed the step
272
- // it was meant to run. The sibling toolchain `@heroiclands/package-build`
273
- // opts into the same two.
274
- .demandCommand(1, "Name a command.")
275
- .strict().argv;
218
+ /** Register the content operations on the package command line. */
219
+ export function registerContentCommands(cli) {
220
+ return cli
221
+ .command(withIndexPreflight(packageCommand(), (_config, argv) => argv.action === "compile"))
222
+ .command(depsCommand())
223
+ .command(docsCommand())
224
+ .command(withIndexPreflight(lintCommand()))
225
+ .command(proseCommand())
226
+ .command(contentFormatCommand())
227
+ .command(withIndexPreflight(linksCommand()))
228
+ .command(formatCommand())
229
+ .command(markdownCommand())
230
+ .command(contentIndexCommand())
231
+ .command(withIndexPreflight(siteCommand(), publishesContentPages))
232
+ .command(
233
+ withIndexPreflight(
234
+ pdfCommand(),
235
+ (config) => config.pdf && publishesContentPages(config),
236
+ ),
237
+ )
238
+ .command(withIndexPreflight(mapCommand()))
239
+ .command(withIndexPreflight(reachabilityCommand()))
240
+ .command(addressesCommand());
241
+ }
242
+
243
+ /** Opt-in readability and plain-language suggestions for Markdown notes. */
244
+ function proseCommand() {
245
+ return {
246
+ command: "prose <action> [path]",
247
+ describe: "Analyze note prose on demand",
248
+ builder: (yargs) =>
249
+ yargs
250
+ .positional("action", { choices: ["lint"], describe: "Analyze prose" })
251
+ .positional("path", {
252
+ type: "string",
253
+ describe: "One Markdown file or a content tree",
254
+ })
255
+ .option("age", { type: "number", describe: "Reader age" })
256
+ .option("threshold", {
257
+ type: "number",
258
+ describe: "Readability algorithms required (1–7)",
259
+ })
260
+ .option("min-words", { type: "number", describe: "Minimum words per sentence" }),
261
+ handler: async (argv) => {
262
+ try {
263
+ const config = loadPackConfig();
264
+ const defaults = loadPackageBuildConfig().proseLint;
265
+ const options = {
266
+ age: argv.age ?? defaults.age,
267
+ threshold: argv.threshold ?? defaults.threshold,
268
+ minWords: argv.minWords ?? defaults.minWords,
269
+ };
270
+ for (const [key, value] of Object.entries(options)) {
271
+ if (
272
+ !Number.isInteger(value) ||
273
+ value < 1 ||
274
+ (key === "threshold" && value > 7)
275
+ ) {
276
+ throw new TypeError(
277
+ `${key} must be ${key === "threshold" ? "an integer from 1 to 7" : "a positive integer"}`,
278
+ );
279
+ }
280
+ }
281
+ const findings = await lintProse(
282
+ argv.path ?? config.paths.content,
283
+ options,
284
+ config.skipDirectories,
285
+ );
286
+ for (const finding of findings) emitDiagnostic(finding);
287
+ log.info(`${findings.length} prose suggestion(s).`);
288
+ } catch (err) {
289
+ reportFailure(err);
290
+ process.exitCode = 1;
291
+ }
292
+ },
293
+ };
294
+ }
295
+
296
+ /** Check declared dependency indexes before an index-consuming command does any work. */
297
+ function withIndexPreflight(command, shouldCheck = () => true) {
298
+ const handler = command.handler;
299
+ return {
300
+ ...command,
301
+ handler: async (argv) => {
302
+ let config;
303
+ try {
304
+ config = loadPackConfig();
305
+ if (shouldCheck(config, argv)) cachedMetadataIndexes(config);
306
+ } catch (err) {
307
+ if (config) {
308
+ emitDiagnostic({
309
+ file: packConfigPath(),
310
+ severity: "error",
311
+ message: err instanceof Error ? err.message : String(err),
312
+ });
313
+ } else reportFailure(err);
314
+ process.exitCode = 1;
315
+ return;
316
+ }
317
+ return handler(argv);
318
+ },
319
+ };
320
+ }
276
321
 
277
322
  /**
278
323
  * `docs item-fields` — render this repository's item-frontmatter reference.
@@ -309,16 +354,21 @@ const argv = yargs(hideBin(process.argv))
309
354
  function docsCommand() {
310
355
  return {
311
356
  command: "docs <action>",
312
- describe: "Generate documentation from the configured registries",
357
+ describe: "Generate and check project documentation",
313
358
  builder: (yargs) => {
314
359
  // Required and honoured, not optional and unread:
315
360
  // the handler rendered the item-field reference whatever it was
316
361
  // given, so the positional constrained what could be typed and
317
362
  // selected nothing.
318
363
  yargs.positional("action", {
319
- describe: "The document to render.",
364
+ describe: "The documentation action.",
320
365
  type: "string",
321
- choices: ["item-fields"],
366
+ choices: ["item-fields", "links", "index"],
367
+ });
368
+ yargs.option("root", {
369
+ describe: "Documentation root for the links and index checks.",
370
+ type: "string",
371
+ default: "docs",
322
372
  });
323
373
  yargs.option("out", {
324
374
  describe: "Write to this file instead of the configured location.",
@@ -337,6 +387,26 @@ function docsCommand() {
337
387
  handler: (argv) => {
338
388
  try {
339
389
  const { action, title, check } = argv;
390
+ if (action === "links" || action === "index") {
391
+ const requestedRoot = path.resolve(argv.root);
392
+ const root =
393
+ fs.existsSync(requestedRoot) ?
394
+ fs.realpathSync(requestedRoot)
395
+ : requestedRoot;
396
+ const findings = action === "links" ? checkDocLinks(root) : checkDocIndex(root);
397
+ for (const finding of findings) {
398
+ emitDiagnostic({
399
+ ...finding,
400
+ file: path.relative(process.cwd(), finding.file),
401
+ });
402
+ }
403
+ if (findings.length) process.exitCode = 1;
404
+ else
405
+ log.info(
406
+ `Documentation ${action} check passed for ${path.relative(process.cwd(), root) || "."}.`,
407
+ );
408
+ return;
409
+ }
340
410
  // Dispatched on, so a second document added here cannot
341
411
  // silently render the first. yargs' `choices` has already
342
412
  // rejected anything unlisted, so the default is unreachable by
@@ -354,7 +424,7 @@ function docsCommand() {
354
424
  const body = `${renderItemFieldReference({
355
425
  title: pageTitle,
356
426
  ...(spec.preamble ? { preamble: spec.preamble } : {}),
357
- generatedBy: "`content-build docs item-fields`",
427
+ generatedBy: "`package-build docs item-fields`",
358
428
  config,
359
429
  })}\n`;
360
430
  // A page filed under the content tree is walked for its
@@ -395,7 +465,7 @@ function docsCommand() {
395
465
  severity: "error",
396
466
  message:
397
467
  "out of date with the item-field declarations " +
398
- "— run `content-build docs item-fields` and " +
468
+ "— run `package-build docs item-fields` and " +
399
469
  "commit the regenerated file",
400
470
  });
401
471
  process.exitCode = 1;
@@ -423,7 +493,7 @@ function docsCommand() {
423
493
  }
424
494
 
425
495
  /**
426
- * `content-build content-format` — check the format specification itself.
496
+ * `package-build content-format` — check the format specification itself.
427
497
  *
428
498
  * Two checks, because the specification makes claims about two different
429
499
  * worlds, and they fail for different reasons and at different times:
@@ -431,15 +501,11 @@ function docsCommand() {
431
501
  * - `schema` compares every `system.*` target the document names against the
432
502
  * naming system's published `schema.json`. A failure means the specification
433
503
  * and the system disagree, which is a defect in one of the two.
434
- * - `fields` compares the per-type tables against the field declarations that
435
- * compile them, so the hand-written half cannot drift from the generated one.
436
- * - `notes` measures a content tree against the vocabulary the document
437
- * declares per type. During the migration it is the progress bar rather
438
- * than a gate, so it **reports** by default and `--strict` makes it fatal —
439
- * turned on one class at a time as each slice lands.
504
+ * - `fields` compares declared field mappings with compiler declarations.
505
+ * - `notes` measures authored notes against the declared vocabulary.
440
506
  *
441
- * Both read the committed document rather than a transcription of it, so
442
- * editing `docs/content-format.md` changes what they assert.
507
+ * The shipped contract is `engine/content-format.yaml`; guide layout does not
508
+ * participate in validation.
443
509
  *
444
510
  * @returns {object} The yargs command module.
445
511
  */
@@ -489,7 +555,7 @@ function contentFormatSchemaCommand() {
489
555
  builder: (yargs) => {
490
556
  yargs.option("spec", {
491
557
  describe:
492
- "The specification to read. Defaults to the docs/content-format.md this package ships.",
558
+ "The format contract to read. Defaults to the engine/content-format.yaml this package ships.",
493
559
  type: "string",
494
560
  });
495
561
  yargs.option("schema", {
@@ -597,7 +663,7 @@ function contentFormatFieldsCommand() {
597
663
  builder: (yargs) => {
598
664
  yargs.option("spec", {
599
665
  describe:
600
- "The specification to read. Defaults to the docs/content-format.md this package ships.",
666
+ "The format contract to read. Defaults to the engine/content-format.yaml this package ships.",
601
667
  type: "string",
602
668
  });
603
669
  yargs.option("fields", {
@@ -686,7 +752,7 @@ function contentFormatNotesCommand() {
686
752
  });
687
753
  yargs.option("spec", {
688
754
  describe:
689
- "The specification to read. Defaults to the docs/content-format.md this package ships.",
755
+ "The format contract to read. Defaults to the engine/content-format.yaml this package ships.",
690
756
  type: "string",
691
757
  });
692
758
  yargs.option("strict", {
@@ -754,7 +820,7 @@ function contentFormatNotesCommand() {
754
820
  }
755
821
 
756
822
  /**
757
- * `content-build lint` — check a content tree's addresses.
823
+ * `package-build lint` — check a content tree's addresses.
758
824
  *
759
825
  * Deliberately independent of the pack pipeline: it compiles nothing, opens no
760
826
  * LevelDB and needs no Foundry manifest, so it runs in a second and can gate a
@@ -910,7 +976,7 @@ function lintCommand() {
910
976
  `@${config.stats.systemVersion ?? "?"}, so emitted ` +
911
977
  `\`system\` fields are unchecked. A system ` +
912
978
  `generates its own; a module gets one from ` +
913
- `\`content-build deps fetch\`.`,
979
+ `\`package-build deps fetch\`.`,
914
980
  );
915
981
  }
916
982
  const fieldSpecs = config.itemFields ?? {};
@@ -1061,7 +1127,7 @@ function lintCommand() {
1061
1127
  }
1062
1128
 
1063
1129
  /**
1064
- * `content-build format` — Prettier, with the shared configuration.
1130
+ * `package-build format` — Prettier, with the shared configuration.
1065
1131
  *
1066
1132
  * Deliberately **not** scoped to the content tree, and deliberately free of the
1067
1133
  * pack configuration: a repository's formatting covers everything it holds, and
@@ -1167,7 +1233,7 @@ function formatCommand() {
1167
1233
  }
1168
1234
 
1169
1235
  /**
1170
- * `content-build markdown` — markdownlint, with the shared rule set.
1236
+ * `package-build markdown` — markdownlint, with the shared rule set.
1171
1237
  *
1172
1238
  * The structural checks Prettier cannot make: a heading level that skips, two
1173
1239
  * sibling headings claiming one anchor, a reversed link, an emphasis marker
@@ -1215,7 +1281,7 @@ function markdownCommand() {
1215
1281
  }
1216
1282
 
1217
1283
  /**
1218
- * `content-build links` — check that every link in a content tree lands.
1284
+ * `package-build links` — check that every link in a content tree lands.
1219
1285
  *
1220
1286
  * Reports a dead `#anchor`, a dead qualified address, and a wikilink authored
1221
1287
  * in frontmatter, plus a vendored manifest that has drifted out of reach. All
@@ -1278,7 +1344,7 @@ function linksCommand() {
1278
1344
  message: `unusable content index: ${s.reason}`,
1279
1345
  });
1280
1346
  }
1281
- log.error("Re-run `content-build deps fetch`.");
1347
+ log.error("Re-run `package-build deps fetch`.");
1282
1348
  process.exitCode = 1;
1283
1349
  return;
1284
1350
  }
@@ -1385,7 +1451,7 @@ function linksCommand() {
1385
1451
  }
1386
1452
 
1387
1453
  /**
1388
- * `content-build content-index` — emit this package's note index.
1454
+ * `package-build content-index` — emit this package's note index.
1389
1455
  *
1390
1456
  * Every build already walks the tree and parses every note's frontmatter, then
1391
1457
  * throws the result away, so nothing outside a build can ask a question about
@@ -1438,7 +1504,7 @@ function contentIndexCommand() {
1438
1504
  }
1439
1505
 
1440
1506
  /**
1441
- * `content-build pdf` — build the book the content tree publishes as.
1507
+ * `package-build pdf` — build the book the content tree publishes as.
1442
1508
  *
1443
1509
  * The third surface, beside `package compile` and `site`. It takes the same
1444
1510
  * `--out` override the site command does, and reports what it found in the same
@@ -1490,6 +1556,9 @@ function pdfCommand() {
1490
1556
  // resolve is an error, because the same statement is wrong in
1491
1557
  // Foundry and on the website too.
1492
1558
  const errors = reportFindings(result.findings, {});
1559
+ for (const report of result.imageReports ?? []) {
1560
+ log.info(`${report.file}: ${report.message}`);
1561
+ }
1493
1562
 
1494
1563
  if (!result.built) {
1495
1564
  // A reason is a deliberate no-op — the fence, an absent
@@ -1523,7 +1592,7 @@ function pdfCommand() {
1523
1592
  }
1524
1593
 
1525
1594
  /**
1526
- * `content-build site` — publish the content tree as a website.
1595
+ * `package-build site` — publish the content tree as a website.
1527
1596
  *
1528
1597
  * The sibling of `package compile`: the same tree, rendered as pages instead of
1529
1598
  * compiled into packs. Everything a consumer would otherwise write for itself —
@@ -1567,6 +1636,8 @@ function siteCommand() {
1567
1636
  config,
1568
1637
  sqlTables: await prepareTreeSqlTables(config.paths.content, {
1569
1638
  skipDirectories: config.skipDirectories,
1639
+ config,
1640
+ audience: "public",
1570
1641
  }),
1571
1642
  });
1572
1643
  const { gates } = result;
@@ -1627,9 +1698,15 @@ function siteCommand() {
1627
1698
  message: e.reason,
1628
1699
  });
1629
1700
  }
1701
+ for (const e of result.expressionErrors) {
1702
+ emitDiagnostic(e);
1703
+ }
1630
1704
  for (const e of result.secretErrors) {
1631
1705
  emitDiagnostic({ ...e, severity: "error" });
1632
1706
  }
1707
+ for (const e of result.captionErrors) {
1708
+ emitDiagnostic({ ...e, severity: "error" });
1709
+ }
1633
1710
  // Reported the way the pack build reports the very same
1634
1711
  // finding: `file:line:column: error: message`, path first, and
1635
1712
  // the message from the shared table, not a
@@ -1666,7 +1743,9 @@ function siteCommand() {
1666
1743
  }
1667
1744
  if (
1668
1745
  result.tableErrors.length ||
1746
+ result.expressionErrors.length ||
1669
1747
  result.secretErrors.length ||
1748
+ result.captionErrors.length ||
1670
1749
  result.wikiErrors.length ||
1671
1750
  result.imageErrors.length
1672
1751
  ) {
@@ -1695,7 +1774,7 @@ function siteCommand() {
1695
1774
  }
1696
1775
 
1697
1776
  /**
1698
- * `content-build map` — draw what the place notes state.
1777
+ * `package-build map` — draw what the place notes state.
1699
1778
  *
1700
1779
  * Reads the content index — this package's and every declared dependency's —
1701
1780
  * so a consumer's map draws its dependencies' places beside its own, and
@@ -1814,7 +1893,7 @@ function mapCommand() {
1814
1893
  });
1815
1894
  }
1816
1895
  if (world.stale.length) {
1817
- log.error("Re-run `content-build deps fetch`.");
1896
+ log.error("Re-run `package-build deps fetch`.");
1818
1897
  process.exitCode = 1;
1819
1898
  return;
1820
1899
  }
@@ -1851,14 +1930,14 @@ function mapCommand() {
1851
1930
  }
1852
1931
 
1853
1932
  /**
1854
- * `content-build reachability <dir> [file]` — check that a corpus reads through.
1933
+ * `package-build reachability <dir> [file]` — check that a corpus reads through.
1855
1934
  *
1856
1935
  * The corpus is named on the command line rather than declared in code, because
1857
1936
  * it never changes for a given repository: a consumer hardcodes the invocation
1858
1937
  * in `package.json` and gets the check without writing a script.
1859
1938
  *
1860
- * content-build reachability Rules --index glossary
1861
- * content-build reachability User_Guide --index glossary
1939
+ * package-build reachability Rules --index glossary
1940
+ * package-build reachability User_Guide --index glossary
1862
1941
  *
1863
1942
  * @returns {object} The yargs command module.
1864
1943
  */
@@ -2118,7 +2197,7 @@ function depsCommand() {
2118
2197
  *
2119
2198
  * ```sh
2120
2199
  * gh release download v0.8.2 -p system.zip -D build/baseline
2121
- * npx content-build addresses diff --from build/baseline/system.zip
2200
+ * npx package-build addresses diff --from build/baseline/system.zip
2122
2201
  * ```
2123
2202
  *
2124
2203
  * @param {object} config - The resolved build configuration.
@@ -2261,10 +2340,10 @@ function addressesCommand() {
2261
2340
  function packageCommand() {
2262
2341
  return {
2263
2342
  command: "package <action> [pack] [entry]",
2264
- describe: "Manage packages",
2343
+ describe: "Compile, unpack, or clean compendium packs",
2265
2344
  builder: (yargs) => {
2266
2345
  // Required, not optional: the action *is* the work, and an
2267
- // optional one meant `content-build package` fell through the
2346
+ // optional one meant `package-build package` fell through the
2268
2347
  // switch below and exited 0 having compiled nothing.
2269
2348
  yargs.positional("action", {
2270
2349
  describe: "The action to perform.",
@@ -2296,8 +2375,8 @@ function packageCommand() {
2296
2375
  throw new Error(
2297
2376
  `\`packageKind: ${config.packageKind}\` compiles no ` +
2298
2377
  `compendium, so there is nothing to ${action}. Build ` +
2299
- `its site with \`content-build site\` and its book ` +
2300
- `with \`content-build pdf\`.`,
2378
+ `its site with \`package-build site\` and its book ` +
2379
+ `with \`package-build pdf\`.`,
2301
2380
  );
2302
2381
  }
2303
2382
  // The one directory the pipeline creates rather than expects: