@heroiclands/package-build 22.22.0 → 22.23.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 (91) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/content-commands.mjs +51 -14
  3. package/ci/content-format-reference.mjs +1 -1
  4. package/content-config.mjs +80 -2
  5. package/docs/api.md +85 -72
  6. package/docs/authoring/assets.md +14 -0
  7. package/docs/authoring/dates-and-calendars.md +37 -1
  8. package/docs/authoring/links-and-markup.md +112 -31
  9. package/docs/commands.md +12 -10
  10. package/docs/configuration.md +90 -13
  11. package/docs/content-format.md +1 -1
  12. package/docs/guides/book.md +5 -4
  13. package/docs/reference/format-details.md +323 -60
  14. package/docs/reference/note-types.md +9 -9
  15. package/engine/anchors.mjs +16 -10
  16. package/engine/art-fields.mjs +118 -0
  17. package/engine/art-slots.mjs +7 -1
  18. package/engine/asset-index.mjs +160 -10
  19. package/engine/asset-replacement.mjs +93 -0
  20. package/engine/asset-types.mjs +240 -0
  21. package/engine/base-compiler.mjs +54 -2
  22. package/engine/bundles.mjs +13 -1
  23. package/engine/content-blocks.mjs +4 -4
  24. package/engine/content-embeds.mjs +19 -0
  25. package/engine/content-figures.mjs +468 -0
  26. package/engine/content-footnotes.mjs +10 -2
  27. package/engine/content-format.yaml +1 -1
  28. package/engine/content-images.mjs +126 -25
  29. package/engine/content-index.mjs +36 -10
  30. package/engine/field-reference.mjs +31 -10
  31. package/engine/field-spec.mjs +10 -8
  32. package/engine/foreign-catalog.mjs +143 -18
  33. package/engine/frontmatter-lint.mjs +73 -1
  34. package/engine/heading-attributes.mjs +31 -18
  35. package/engine/helpers.mjs +97 -30
  36. package/engine/infobox-registry.mjs +4 -1
  37. package/engine/infobox.mjs +47 -8
  38. package/engine/itinerary-scenes.mjs +4 -10
  39. package/engine/journals.mjs +264 -39
  40. package/engine/lore-events.mjs +197 -0
  41. package/engine/markdown-expressions.mjs +257 -7
  42. package/engine/note-dates.mjs +305 -12
  43. package/engine/note-vocabulary.mjs +11 -5
  44. package/engine/pathnames.mjs +36 -0
  45. package/engine/pdf-build.mjs +357 -34
  46. package/engine/pdf-images.mjs +64 -0
  47. package/engine/pdf-render.mjs +160 -36
  48. package/engine/scenes.mjs +16 -4
  49. package/engine/site-build.mjs +127 -13
  50. package/engine/site-index.mjs +72 -1
  51. package/engine/site-preview.mjs +2 -0
  52. package/engine/system-block.mjs +44 -16
  53. package/hm3/actors.mjs +2 -2
  54. package/manifest.mjs +30 -24
  55. package/package.json +1 -1
  56. package/packagebuild-metadata.jsonl +13 -13
  57. package/sohl/item-fields.mjs +4 -3
  58. package/types/content-config.d.mts +48 -1
  59. package/types/engine/anchors.d.mts +9 -4
  60. package/types/engine/art-fields.d.mts +64 -0
  61. package/types/engine/asset-index.d.mts +43 -0
  62. package/types/engine/asset-replacement.d.mts +22 -0
  63. package/types/engine/asset-types.d.mts +18 -0
  64. package/types/engine/base-compiler.d.mts +0 -17
  65. package/types/engine/content-embeds.d.mts +16 -0
  66. package/types/engine/content-figures.d.mts +101 -0
  67. package/types/engine/content-footnotes.d.mts +7 -0
  68. package/types/engine/content-images.d.mts +78 -12
  69. package/types/engine/content-index.d.mts +8 -2
  70. package/types/engine/field-reference.d.mts +21 -0
  71. package/types/engine/field-spec.d.mts +20 -16
  72. package/types/engine/foreign-catalog.d.mts +39 -0
  73. package/types/engine/heading-attributes.d.mts +13 -8
  74. package/types/engine/helpers.d.mts +23 -7
  75. package/types/engine/infobox-registry.d.mts +4 -1
  76. package/types/engine/infobox.d.mts +6 -0
  77. package/types/engine/journals.d.mts +31 -9
  78. package/types/engine/lore-events.d.mts +14 -0
  79. package/types/engine/markdown-expressions.d.mts +23 -2
  80. package/types/engine/note-dates.d.mts +64 -1
  81. package/types/engine/pathnames.d.mts +26 -0
  82. package/types/engine/pdf-build.d.mts +28 -6
  83. package/types/engine/pdf-images.d.mts +52 -0
  84. package/types/engine/pdf-render.d.mts +12 -0
  85. package/types/engine/site-build.d.mts +14 -3
  86. package/types/engine/site-index.d.mts +54 -0
  87. package/types/engine/system-block.d.mts +26 -2
  88. package/types/hm3/actors.d.mts +18 -0
  89. package/types/manifest.d.mts +19 -23
  90. package/engine/content-captions.mjs +0 -230
  91. package/types/engine/content-captions.d.mts +0 -58
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ **Figures**
8
+
9
+ - A picture and the words about it are written as one block: the caption is optional, several pictures can share one, and a border is available.
10
+ - Prose refers to a figure by name, in its own note or in another, and reads back its number, its number and caption, or the caption alone.
11
+ - A figure drawn from a map counts as a Map, numbered the same way on every surface.
12
+ - A figure holding one picture opens as an illustrated page in Foundry, with that viewer's own pop-out and zoom.
13
+
14
+ **Pictures**
15
+
16
+ - A picture carrying no caption draws no line beneath it, on a page, in a journal or in print.
17
+ - A picture states what it is for, and is drawn at the size that choice gives it — never larger than it was made.
18
+ - A page's hero image is 1792×768, and one of any other size is refused, naming the size to cut it to.
19
+
20
+ **Art provenance**
21
+
22
+ - A record saying a file is not machine-generated is published saying so.
23
+ - A package may refuse generated art in its own tree.
24
+ - A package may declare that a dependency's art answers its own addresses, and the book draws the replacement.
25
+
26
+ **Notes**
27
+
28
+ - A lore note states the occasions it records, each with a date and an optional recurrence, and publishes the next one due.
29
+ - A note may write a shared field at the position it is being retired from, and the build accepts it with a warning rather than refusing the note.
30
+
31
+ ### Patch Changes
32
+
33
+ **Item-frontmatter reference**: regenerating the page no longer renders a broken sentence for `affiliation`'s `title`, which now says plainly that a top-level key is refused outright.
34
+
3
35
  ## 22.22.0
4
36
 
5
37
  ### Minor Changes
@@ -64,6 +64,7 @@ import {
64
64
  import { fetchNavigation, generateHugoConfig, writeHugoConfig } from "../engine/site-config.mjs";
65
65
  import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
66
66
  import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
67
+ import { checkArtSlotSizes } from "../engine/art-fields.mjs";
67
68
  import { lintContentTree } from "../engine/content-lint.mjs";
68
69
  import { lintNoteStates } from "../engine/stub-lint.mjs";
69
70
  import { lintContentCharset } from "../engine/content-charset.mjs";
@@ -109,6 +110,7 @@ import {
109
110
  authoredFrontmatter,
110
111
  emitContentIndex,
111
112
  indexRecordsFor,
113
+ isAssetRecord,
112
114
  isNoteRecord,
113
115
  noteFile,
114
116
  } from "../engine/content-index.mjs";
@@ -139,6 +141,7 @@ import { buildMaps, FROM_ALL, MAP_DIR } from "../engine/map-build.mjs";
139
141
  import { CHART_HORIZON_DAYS } from "../engine/map-layout.mjs";
140
142
  import { GRAPHVIZ_ENGINES } from "../engine/map-graphviz.mjs";
141
143
  import { loadMapWorld } from "../engine/map-places.mjs";
144
+ import { encodeAddresses } from "../engine/address-values.mjs";
142
145
 
143
146
  /**
144
147
  * The packs `unpack` extracts.
@@ -1175,6 +1178,32 @@ function lintCommand() {
1175
1178
  });
1176
1179
  for (const line of states.summary) log.info(line);
1177
1180
 
1181
+ // Generated art, when this package declares it carries none.
1182
+ // `records` already holds every asset of this build, so the
1183
+ // check is a filter over the corpus already in hand rather
1184
+ // than a second enumeration of the asset trees.
1185
+ const generatedArt =
1186
+ config.forbidGeneratedArt ?
1187
+ records
1188
+ .filter(isAssetRecord)
1189
+ .filter((record) => record.asset.ai === true)
1190
+ .map((record) => ({
1191
+ file: path.join(config.paths.assets, record.asset.path),
1192
+ severity: "error",
1193
+ message:
1194
+ `\`${encodeAddresses(record.address.canonical)}\` is ` +
1195
+ "machine-generated art (`ai: true`), and " +
1196
+ "`forbidGeneratedArt` refuses it in this package",
1197
+ }))
1198
+ : [];
1199
+
1200
+ // A hero image against the size its slot is cut to, read
1201
+ // from the records already in hand.
1202
+ const artSizes = checkArtSlotSizes(records, {
1203
+ config,
1204
+ assetsBase: config.paths.assets,
1205
+ });
1206
+
1178
1207
  const findings = [
1179
1208
  ...addresses.findings,
1180
1209
  ...frontmatter.findings,
@@ -1185,6 +1214,8 @@ function lintCommand() {
1185
1214
  ...images.findings,
1186
1215
  ...taskLists.findings,
1187
1216
  ...states.findings,
1217
+ ...generatedArt,
1218
+ ...artSizes,
1188
1219
  ];
1189
1220
  // Only an **error** fails the run. Every finding was an error
1190
1221
  // by then, so this changes nothing on its own —
@@ -1820,6 +1851,9 @@ function siteCommand() {
1820
1851
  for (const e of result.footnoteErrors) {
1821
1852
  emitDiagnostic({ ...e, severity: "error" });
1822
1853
  }
1854
+ for (const e of result.embedErrors) {
1855
+ emitDiagnostic({ ...e, severity: "error" });
1856
+ }
1823
1857
  // Reported the way the pack build reports the very same
1824
1858
  // finding: `file:line:column: error: message`, path first, and
1825
1859
  // the message from the shared table, not a
@@ -1862,7 +1896,8 @@ function siteCommand() {
1862
1896
  result.headingErrors.length ||
1863
1897
  result.footnoteErrors.length ||
1864
1898
  result.wikiErrors.length ||
1865
- result.imageErrors.length
1899
+ result.imageErrors.length ||
1900
+ result.embedErrors.length
1866
1901
  ) {
1867
1902
  process.exitCode = 1;
1868
1903
  return;
@@ -2195,16 +2230,18 @@ async function fetchFromLocalArtifact(config, argv) {
2195
2230
 
2196
2231
  /**
2197
2232
  * `deps fetch` — fill the caches this build resolves other packages through:
2198
- * the **content index** of every declared dependency, the **item catalogue**
2199
- * of those additionally declaring `itemCatalog: true`, and the **site
2200
- * navigation** heroiclands.org publishes, which the site build writes its
2201
- * menu from.
2202
- *
2203
- * The two dependency sets differ deliberately. Citing another package's
2204
- * *addresses* and embedding its *items* are separate edges, and a package may
2205
- * have either without the other — `harn-ensemble` cites no foreign address and
2206
- * embeds 324,016 item references. The navigation is fetched for every package,
2207
- * because every package publishes a site.
2233
+ * the **content index** of every declared dependency, the **item catalogue
2234
+ * or asset archive** of those additionally declaring `itemCatalog: true` or
2235
+ * `assetArchive: true`, and the **site navigation** heroiclands.org
2236
+ * publishes, which the site build writes its menu from.
2237
+ *
2238
+ * The dependency sets differ deliberately. Citing another package's
2239
+ * *addresses*, embedding its *items*, and staging its *asset bytes* are
2240
+ * separate edges, and a package may declare any of them without the others —
2241
+ * `harn-ensemble` cites no foreign address and embeds 324,016 item
2242
+ * references. The navigation is fetched for every package, because every
2243
+ * package publishes a site. A relationship declaring both `itemCatalog: true`
2244
+ * and `assetArchive: true` is fetched once.
2208
2245
  *
2209
2246
  * Its own command rather than a step of `package compile` or `site`, so that
2210
2247
  * neither reaches the network. A build that downloads silently is not
@@ -2265,7 +2302,7 @@ function depsCommand() {
2265
2302
  const indexes = await fetchAllMetadata(config);
2266
2303
  if (indexes) log.info(`Fetched ${indexes} dependency content index(es).`);
2267
2304
  const count = await fetchAllCatalogs(config);
2268
- if (count) log.info(`Fetched ${count} dependency catalogue(s).`);
2305
+ if (count) log.info(`Fetched ${count} dependency catalogue(s) or archive(s).`);
2269
2306
  if (!indexes && !count) {
2270
2307
  // Distinguished from a package declaring nothing at all: a
2271
2308
  // relationship may still be declared, just narrowed to the
@@ -2277,8 +2314,8 @@ function depsCommand() {
2277
2314
  );
2278
2315
  log.info(
2279
2316
  declared ?
2280
- "No declared dependency needs a fetched content index or item " +
2281
- "catalogue."
2317
+ "No declared dependency needs a fetched content index, item " +
2318
+ "catalogue, or asset archive."
2282
2319
  : "This package declares no dependencies.",
2283
2320
  );
2284
2321
  }
@@ -137,7 +137,7 @@ const GENERATED_TABLES = [
137
137
  "| ------ | ---------- | ----------------- |",
138
138
  ...Object.entries(EXPRESSION_HELPERS).map(
139
139
  ([name, { params, summary }]) =>
140
- `| \`${name}\` | \`${params}\` | ${cell(summary)} |`,
140
+ `| \`${cell(name)}\` | \`${cell(params)}\` | ${cell(summary)} |`,
141
141
  ),
142
142
  ],
143
143
  },
@@ -368,7 +368,8 @@ export function publishesContentPages(config) {
368
368
  * @property {string} [stage] Compiled LevelDB packs.
369
369
  * @property {string} [unpack] Where `unpack` extracts JSON back to.
370
370
  * @property {string} [foreignCache] Where a dependency declaring
371
- * `itemCatalog: true` is unpacked.
371
+ * `itemCatalog: true` or
372
+ * `assetArchive: true` is unpacked.
372
373
  * Inbound, and fetched rather than
373
374
  * committed.
374
375
  * @property {string} [metadataCache] Where a dependency's published content
@@ -486,6 +487,15 @@ export function publishesContentPages(config) {
486
487
  * package this one targets — for a system
487
488
  * relationship, `verified` is what
488
489
  * `_stats.systemVersion` is stamped from.
490
+ * @property {boolean} [itemCatalog] Whether `deps fetch` unpacks this
491
+ * dependency's release archive and extracts
492
+ * its Item packs, so the actors pass can
493
+ * resolve embedded items this repository
494
+ * does not hold. Needs a `manifest`.
495
+ * @property {boolean} [assetArchive] Whether `deps fetch` unpacks this
496
+ * dependency's release archive for its
497
+ * asset bytes alone, building no item
498
+ * catalogue from it. Needs a `manifest`.
489
499
  * @property {boolean} [contentIndex] Whether `deps fetch` fetches this
490
500
  * dependency's content index. Default
491
501
  * `true`. `false` declares the dependency
@@ -494,6 +504,13 @@ export function publishesContentPages(config) {
494
504
  * `itemCatalog: true` on the same entry,
495
505
  * since a catalogue is fetched from the same
496
506
  * index.
507
+ * @property {boolean} [assetReplacement] Whether this dependency's asset
508
+ * tree answers this package's own asset
509
+ * addresses ahead of its local record.
510
+ * Default `false`. Refuses `contentIndex:
511
+ * false` on the same entry, since a
512
+ * replacement with no fetched index to
513
+ * check against resolves nothing.
497
514
  */
498
515
 
499
516
  /**
@@ -620,6 +637,9 @@ export function publishesContentPages(config) {
620
637
  * @property {string[]} [skipDirectories] Directory names the content walk ignores
621
638
  * wherever they appear (e.g. Obsidian's
622
639
  * `Templates`). Default `[]`.
640
+ * @property {boolean} [forbidGeneratedArt] Whether `lint` refuses an asset
641
+ * record of this package carrying
642
+ * `ai: true`. Default `false`.
623
643
  * @property {PackageBuildSection} [packageBuild] Reserved for
624
644
  * `@heroiclands/package-build`, which
625
645
  * validates it. Not read here.
@@ -700,6 +720,8 @@ export function publishesContentPages(config) {
700
720
  * types. The one set the compilers and the
701
721
  * link-manifest emitter both read.
702
722
  * @property {readonly string[]} skipDirectories
723
+ * @property {boolean} forbidGeneratedArt Whether `lint` refuses an asset
724
+ * record of this package carrying `ai: true`.
703
725
  * @property {import("./engine/content-icons.mjs").IconRegistry} icons The
704
726
  * fonts this package ships and the names it
705
727
  * draws from them; empty when it declares
@@ -728,6 +750,7 @@ const CONFIG_KEYS = [
728
750
  "itemBuilders",
729
751
  "paths",
730
752
  "skipDirectories",
753
+ "forbidGeneratedArt",
731
754
  "icons",
732
755
  "packs",
733
756
  "docs",
@@ -765,14 +788,25 @@ const PDF_FONT_KEYS = ["serif", "sans", "mono", "path"];
765
788
  const EMPTY_PDF_FONTS = Object.freeze({ serif: "", sans: "", mono: "", path: "" });
766
789
  const DOC_PAGE_KEYS = ["title", "out", "preamble", "frontmatter"];
767
790
  const RELATIONSHIP_KINDS = ["systems", "requires", "recommends", "conflicts"];
768
- const RELATIONSHIP_KEYS = [
791
+ /**
792
+ * Every key a declared relationship may carry.
793
+ *
794
+ * Read by the manifest writer as well as by the configuration check: the keys
795
+ * Foundry's own relationship schema does not name are the build's own, and the
796
+ * published manifest carries none of them.
797
+ *
798
+ * @type {readonly string[]}
799
+ */
800
+ export const RELATIONSHIP_KEYS = [
769
801
  "id",
770
802
  "contentPackage",
771
803
  "type",
772
804
  "manifest",
773
805
  "compatibility",
774
806
  "itemCatalog",
807
+ "assetArchive",
775
808
  "contentIndex",
809
+ "assetReplacement",
776
810
  ];
777
811
  const AUTHOR_KEYS = ["name", "email", "url"];
778
812
  const ITEM_BUILDER_KEYS = ["system", "img", "fields"];
@@ -2198,6 +2232,23 @@ function normalizeRelationships(value) {
2198
2232
  }
2199
2233
  spec.itemCatalog = rel.itemCatalog;
2200
2234
  }
2235
+ // Opt-in, sibling to `itemCatalog`: unpack this dependency's
2236
+ // release archive for its asset bytes alone, building no item
2237
+ // catalogue from it. Buys the archive for a package with no
2238
+ // items of its own to extract — an asset-replacement source
2239
+ // ships no Item packs and has no use for `itemCatalog: true`.
2240
+ if (rel.assetArchive !== undefined) {
2241
+ if (typeof rel.assetArchive !== "boolean") {
2242
+ fail(`${at}.assetArchive`, "must be true or false");
2243
+ }
2244
+ if (rel.assetArchive && spec.manifest === undefined) {
2245
+ fail(
2246
+ `${at}.assetArchive`,
2247
+ "needs a `manifest` naming the package to fetch",
2248
+ );
2249
+ }
2250
+ spec.assetArchive = rel.assetArchive;
2251
+ }
2201
2252
  // Opt-out: declares the dependency for the Foundry manifest
2202
2253
  // only, so `deps fetch` fetches no content index for it and a
2203
2254
  // wikilink into it is refused rather than silently dead. A
@@ -2216,6 +2267,26 @@ function normalizeRelationships(value) {
2216
2267
  }
2217
2268
  spec.contentIndex = rel.contentIndex;
2218
2269
  }
2270
+ // Opt-in: this dependency's asset tree answers this
2271
+ // package's own asset addresses ahead of its local record —
2272
+ // the "rewrite table" `engine/asset-replacement.mjs` reads.
2273
+ // A replacement with no fetched index to check against
2274
+ // resolves nothing, so it cannot pair with
2275
+ // `contentIndex: false`.
2276
+ if (rel.assetReplacement !== undefined) {
2277
+ if (typeof rel.assetReplacement !== "boolean") {
2278
+ fail(`${at}.assetReplacement`, "must be true or false");
2279
+ }
2280
+ if (rel.assetReplacement && rel.contentIndex === false) {
2281
+ fail(
2282
+ `${at}.assetReplacement`,
2283
+ "cannot be true together with `contentIndex: false` — a " +
2284
+ "replacement with no fetched index to check against resolves " +
2285
+ "nothing",
2286
+ );
2287
+ }
2288
+ spec.assetReplacement = rel.assetReplacement;
2289
+ }
2219
2290
  return Object.freeze(spec);
2220
2291
  }),
2221
2292
  );
@@ -2694,6 +2765,12 @@ export function defineConfig(config) {
2694
2765
  requireNonEmptyString(name, `skipDirectories[${index}]`),
2695
2766
  );
2696
2767
 
2768
+ const forbidGeneratedArt = optionalBoolean(
2769
+ input.forbidGeneratedArt,
2770
+ "forbidGeneratedArt",
2771
+ false,
2772
+ );
2773
+
2697
2774
  // Refused above for a documentation package, so there is nothing to read
2698
2775
  // and nothing to derive an asset root or a package-wide system from.
2699
2776
  const foundryPackage =
@@ -2800,6 +2877,7 @@ export function defineConfig(config) {
2800
2877
  itemTypes,
2801
2878
  docEntryTypes,
2802
2879
  skipDirectories: Object.freeze(skipDirectories),
2880
+ forbidGeneratedArt,
2803
2881
  icons: normalizeIcons(input.icons, rootDir),
2804
2882
  packs: Object.freeze(packs),
2805
2883
  packDirectories: Object.freeze(packDirectories),