@heroiclands/package-build 19.0.0 → 20.2.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 (85) hide show
  1. package/CHANGELOG.md +1100 -0
  2. package/CONTENT.md +264 -33
  3. package/README.md +43 -4
  4. package/bin/content-build.mjs +94 -3
  5. package/config.mjs +9 -1
  6. package/content-config.mjs +99 -19
  7. package/docs/content-format.md +394 -72
  8. package/e2e.mjs +297 -3
  9. package/engine/actor-compiler.mjs +197 -7
  10. package/engine/address-charset.mjs +23 -5
  11. package/engine/base-compiler.mjs +63 -2
  12. package/engine/bundles.mjs +9 -0
  13. package/engine/content-address.mjs +92 -1
  14. package/engine/content-charset.mjs +434 -0
  15. package/engine/content-format.mjs +102 -0
  16. package/engine/content-icons.mjs +388 -0
  17. package/engine/content-index.mjs +11 -8
  18. package/engine/content-links.mjs +37 -21
  19. package/engine/field-reference.mjs +57 -5
  20. package/engine/field-spec.mjs +214 -7
  21. package/engine/folder-notes.mjs +24 -1
  22. package/engine/foreign-catalog.mjs +112 -7
  23. package/engine/foundry-entries.mjs +14 -0
  24. package/engine/frontmatter-lint.mjs +377 -56
  25. package/engine/frontmatter.mjs +11 -11
  26. package/engine/generate.mjs +72 -12
  27. package/engine/helpers.mjs +96 -10
  28. package/engine/index.mjs +9 -0
  29. package/engine/item-compiler.mjs +37 -0
  30. package/engine/journals.mjs +21 -4
  31. package/engine/macros.mjs +8 -0
  32. package/engine/map-notes.mjs +7 -7
  33. package/engine/note-claims.mjs +208 -5
  34. package/engine/note-ids.mjs +25 -1
  35. package/engine/note-vocabulary.mjs +76 -9
  36. package/engine/pack-config.mjs +102 -12
  37. package/engine/pack-router.mjs +0 -0
  38. package/engine/prose-config.mjs +42 -0
  39. package/engine/prose-lint.mjs +126 -0
  40. package/engine/retired-fields.mjs +57 -16
  41. package/engine/runtime-only-fields.mjs +204 -0
  42. package/engine/scenes.mjs +12 -19
  43. package/engine/schema-check.mjs +23 -1
  44. package/engine/schema-extract.mjs +13 -0
  45. package/engine/site-index.mjs +17 -0
  46. package/engine/subtype-registry.mjs +30 -0
  47. package/engine/system-block.mjs +81 -3
  48. package/engine/web-wikilinks.mjs +33 -27
  49. package/engine/wikilink-syntax.mjs +7 -0
  50. package/engine/wikilinks.mjs +74 -16
  51. package/hm3/actors.mjs +63 -13
  52. package/package.json +2 -2
  53. package/sohl/actors.mjs +106 -7
  54. package/sohl/item-fields.mjs +203 -0
  55. package/sohl/note-schemas.mjs +6 -3
  56. package/types/config.d.mts +7 -0
  57. package/types/e2e.d.mts +130 -3
  58. package/types/engine/actor-compiler.d.mts +83 -3
  59. package/types/engine/address-charset.d.mts +22 -4
  60. package/types/engine/base-compiler.d.mts +54 -3
  61. package/types/engine/content-address.d.mts +64 -0
  62. package/types/engine/content-charset.d.mts +127 -0
  63. package/types/engine/content-format.d.mts +9 -0
  64. package/types/engine/content-icons.d.mts +151 -0
  65. package/types/engine/field-spec.d.mts +271 -3
  66. package/types/engine/folder-notes.d.mts +20 -0
  67. package/types/engine/foreign-catalog.d.mts +38 -2
  68. package/types/engine/foundry-entries.d.mts +6 -0
  69. package/types/engine/frontmatter-lint.d.mts +164 -30
  70. package/types/engine/frontmatter.d.mts +11 -11
  71. package/types/engine/generate.d.mts +27 -0
  72. package/types/engine/helpers.d.mts +45 -9
  73. package/types/engine/index.d.mts +3 -0
  74. package/types/engine/map-notes.d.mts +2 -2
  75. package/types/engine/note-claims.d.mts +67 -0
  76. package/types/engine/note-ids.d.mts +14 -0
  77. package/types/engine/pack-config.d.mts +35 -0
  78. package/types/engine/prose-config.d.mts +41 -0
  79. package/types/engine/prose-lint.d.mts +36 -0
  80. package/types/engine/retired-fields.d.mts +29 -13
  81. package/types/engine/runtime-only-fields.d.mts +102 -0
  82. package/types/engine/schema-check.d.mts +10 -1
  83. package/types/engine/subtype-registry.d.mts +21 -0
  84. package/types/engine/system-block.d.mts +28 -2
  85. package/types/sohl/actors.d.mts +3 -3
@@ -69,6 +69,7 @@ import path from "node:path";
69
69
  // a cycle around a consumer's config file (see `engine/pack-config.mjs`).
70
70
  import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./engine/address-charset.mjs";
71
71
  import { MAP_TYPES, PACK_BY_TYPE } from "./engine/ids.mjs";
72
+ import { ACTOR_TYPES } from "./engine/subtype-registry.mjs";
72
73
  import { NOTE_VOCABULARY } from "./engine/note-vocabulary.mjs";
73
74
 
74
75
  /**
@@ -1369,6 +1370,52 @@ function normalizeCompatibility(value, where, requireMinimum = true) {
1369
1370
  * @param {unknown} value - The declared `systems:` mapping.
1370
1371
  * @returns {Readonly<Record<string, Readonly<object>>>} Frozen; `{}` when absent.
1371
1372
  */
1373
+ /**
1374
+ * The **package-wide** system, or `null` where the configuration names none
1375
+ * (#48).
1376
+ *
1377
+ * A *system* package is its own system, which is true by construction and needs
1378
+ * no declaration. A *module* takes the one it requires, or the one system it
1379
+ * declares when there is exactly one; with several and no gate there is no
1380
+ * package-wide answer, and each pack carries its own.
1381
+ *
1382
+ * A lone `relationships.systems` entry is a declaration of the system as much as
1383
+ * a gate, so it still answers. That matters because the relationship carries
1384
+ * `itemCatalog` too — a separate concern the `systems:` split does not replace —
1385
+ * so a repository using it would otherwise have to restate its compatibility
1386
+ * under `systems:` purely to keep stamping, which is the duplication that split
1387
+ * exists to remove. Several entries have no single answer and get none.
1388
+ *
1389
+ * **Written once and read twice**, which is why it is a function rather than the
1390
+ * expression it used to be: the value stamped into `stats.systemId` and the
1391
+ * value a pack's `system:` is validated against are the same fact, and two
1392
+ * spellings of it would be free to disagree about exactly the case that has no
1393
+ * answer.
1394
+ *
1395
+ * @param {object} parts - The resolved pieces of the configuration.
1396
+ * @param {string} parts.packageKind - `systems` or `modules`.
1397
+ * @param {unknown} parts.foundryPackage - The package id.
1398
+ * @param {string|null} parts.requiresSystem - The declared gate, if any.
1399
+ * @param {Readonly<Record<string, object>>} parts.systems - The `systems:` block.
1400
+ * @param {readonly {id?: string}[]} parts.relationshipSystems - System
1401
+ * relationships.
1402
+ * @returns {string|null} The system id, or `null` where there is no single one.
1403
+ */
1404
+ function packageWideSystemId({
1405
+ packageKind,
1406
+ foundryPackage,
1407
+ requiresSystem,
1408
+ systems,
1409
+ relationshipSystems,
1410
+ }) {
1411
+ if (packageKind === "systems") return /** @type {string} */ (foundryPackage);
1412
+ if (requiresSystem) return requiresSystem;
1413
+ const declared = Object.keys(systems);
1414
+ if (declared.length === 1) return declared[0];
1415
+ if (relationshipSystems.length === 1) return relationshipSystems[0]?.id ?? null;
1416
+ return null;
1417
+ }
1418
+
1372
1419
  function normalizeSystems(value) {
1373
1420
  if (value === undefined || value === null) return Object.freeze({});
1374
1421
  if (!isPlainObject(value)) fail("systems", "must be a mapping of id to spec");
@@ -1874,6 +1921,15 @@ export function defineConfig(config) {
1874
1921
  const relationshipSystems = /** @type {{id?: string}[]} */ (
1875
1922
  (isPlainObject(input.relationships) ? input.relationships.systems : null) ?? []
1876
1923
  );
1924
+ // Read here as well as stamped below, so the check that a pack's `system:`
1925
+ // resolves to something and the value it resolves to are one statement.
1926
+ const packageWide = packageWideSystemId({
1927
+ packageKind,
1928
+ foundryPackage: input.foundryPackage,
1929
+ requiresSystem,
1930
+ systems,
1931
+ relationshipSystems,
1932
+ });
1877
1933
 
1878
1934
  // A name that resolves to nothing is a build error rather than a
1879
1935
  // fall-through, in the spirit the rest of this file already follows: a pack
@@ -1890,11 +1946,35 @@ export function defineConfig(config) {
1890
1946
  }
1891
1947
  for (const pack of packs.flatMap((p) => [p, ...p.companions])) {
1892
1948
  if (!pack.system) continue;
1893
- if (declaredSystems.size && !declaredSystems.has(pack.system)) {
1949
+ // **A pack's `system:` must resolve to a stamp**, and there are exactly
1950
+ // two things it can resolve to: a `systems:` entry, which carries the
1951
+ // verified version `statsForPack` reads, or this package's own
1952
+ // package-wide system, whose stats answer for every pack of it.
1953
+ //
1954
+ // This used to be skipped entirely when `systems:` was empty or absent
1955
+ // — `declaredSystems.size &&` guarded it — which left the case the
1956
+ // comment above was written about wide open. `harn-ensemble` declares
1957
+ // `system: sohl` and `system: hm3` on its packs, no `systems:` block,
1958
+ // and no package-wide system, so every pack fell through to a
1959
+ // package-wide stat that is null: 2,513 compiled actors stamped
1960
+ // `_stats.systemId: null` in a pack that says `system: sohl` on the
1961
+ // line above. That is the plausible lie #43 was about, reached by the
1962
+ // one path this check did not cover, and the `requiresSystem` check ten
1963
+ // lines up already refuses its own version of it in as many words.
1964
+ if (!declaredSystems.has(pack.system) && pack.system !== packageWide) {
1894
1965
  fail(
1895
1966
  `packs.${pack.name}.system`,
1896
- `names \`${pack.system}\`, which \`systems:\` does not ` +
1897
- `declare. Declared: ${[...declaredSystems].join(", ")}`,
1967
+ `names \`${pack.system}\`, which \`systems:\` does not declare` +
1968
+ (declaredSystems.size ?
1969
+ ` (declared: ${[...declaredSystems].join(", ")})`
1970
+ : ` — the \`systems:\` block is empty or absent`) +
1971
+ (packageWide ?
1972
+ `, and which is not this package's own system \`${packageWide}\``
1973
+ : `, and this package has no package-wide system either`) +
1974
+ `. Every document in the pack is stamped \`_stats.systemId\` ` +
1975
+ `and \`systemVersion\` from one of those two, so with ` +
1976
+ `neither it would be stamped null. Add \`systems:\` naming ` +
1977
+ `\`${pack.system}\` with a \`compatibility.verified\` version`,
1898
1978
  );
1899
1979
  }
1900
1980
  // With a gate set, a pack for any other system could never be seen:
@@ -1953,7 +2033,15 @@ export function defineConfig(config) {
1953
2033
  // holds every key any of them declares, so this stays "the registry's keys"
1954
2034
  // rather than becoming a second list to keep in step (#1504).
1955
2035
  const itemTypes = Object.freeze(new Set(Object.keys(itemBuilders)));
1956
- const docEntryTypes = Object.freeze(new Set([...itemTypes, "macro", ...MAP_TYPES]));
2036
+ // Every note that compiles into a *system-bearing* document publishes its
2037
+ // prose as a documentation JournalEntry, and that includes actors (#337).
2038
+ // A being was the one such note with no `none` address — its only address
2039
+ // named the Actor — so nothing a prose link wrote could land on its page.
2040
+ // `doc` stays out for the reason that actually applies to it: its single
2041
+ // document *is* the prose.
2042
+ const docEntryTypes = Object.freeze(
2043
+ new Set([...itemTypes, ...ACTOR_TYPES, "macro", ...MAP_TYPES]),
2044
+ );
1957
2045
 
1958
2046
  return Object.freeze({
1959
2047
  rootDir,
@@ -1970,21 +2058,13 @@ export function defineConfig(config) {
1970
2058
  // when there is exactly one; with several and no gate there is no
1971
2059
  // package-wide answer, and each pack carries its own.
1972
2060
  stats: normalizeStats(input.stats, {
1973
- systemId:
1974
- packageKind === "systems" ? foundryPackage
1975
- : requiresSystem ? requiresSystem
1976
- : Object.keys(systems).length === 1 ? Object.keys(systems)[0]
1977
- // A lone `relationships.systems` entry is a declaration of
1978
- // the system as much as a gate, so it still answers. That
1979
- // matters because the relationship carries `itemCatalog`
1980
- // too — a separate concern the split does not replace — so
1981
- // a repository using it would otherwise have to restate its
1982
- // compatibility under `systems:` purely to keep stamping,
1983
- // which is the duplication this whole change exists to
1984
- // remove. Several entries have no single answer and get
1985
- // none.
1986
- : relationshipSystems.length === 1 ? (relationshipSystems[0]?.id ?? null)
1987
- : null,
2061
+ systemId: packageWideSystemId({
2062
+ packageKind,
2063
+ foundryPackage,
2064
+ requiresSystem,
2065
+ systems,
2066
+ relationshipSystems,
2067
+ }),
1988
2068
  // Derived here where the answer is pure data — the `verified` of
1989
2069
  // whichever system the package-wide block takes — and supplied by
1990
2070
  // the loader otherwise. The loader is the half that may do I/O, and