@heroiclands/package-build 17.1.0 → 18.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.
- package/CHANGELOG.md +1773 -0
- package/CONTENT.md +288 -72
- package/README.md +1 -1
- package/bin/content-build.mjs +218 -137
- package/bin/package-build.mjs +43 -0
- package/content-config.mjs +62 -67
- package/docs/content-format.md +768 -58
- package/engine/actor-compiler.mjs +586 -0
- package/engine/address-charset.mjs +11 -8
- package/engine/address-diff.mjs +266 -22
- package/engine/anchored-sections.mjs +83 -0
- package/engine/anchors.mjs +83 -0
- package/engine/base-compiler.mjs +148 -18
- package/engine/bundle-notes.mjs +276 -0
- package/engine/bundles.mjs +307 -0
- package/engine/code-fences.mjs +103 -0
- package/engine/compile-corpus.mjs +89 -0
- package/engine/content-address.mjs +267 -5
- package/engine/content-format-check.mjs +54 -11
- package/engine/content-format.mjs +37 -5
- package/engine/content-index.mjs +394 -111
- package/engine/content-links.mjs +185 -77
- package/engine/content-lint.mjs +232 -14
- package/engine/content-package.mjs +2 -1
- package/engine/content-tables.mjs +99 -3
- package/engine/document-subtypes.mjs +49 -6
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +56 -6
- package/engine/folder-notes.mjs +470 -0
- package/engine/foreign-catalog.mjs +195 -4
- package/engine/{manifest-emit.mjs → foundry-entries.mjs} +78 -81
- package/engine/frontmatter-lint.mjs +245 -19
- package/engine/frontmatter.mjs +88 -8
- package/engine/generate.mjs +304 -13
- package/engine/helpers.mjs +293 -64
- package/engine/ids.mjs +137 -2
- package/engine/index-records.mjs +126 -0
- package/engine/index.mjs +23 -5
- package/engine/item-compiler.mjs +349 -0
- package/engine/item-docs.mjs +7 -1
- package/engine/item-registry.mjs +6 -0
- package/engine/journals.mjs +93 -27
- package/engine/macros.mjs +4 -2
- package/engine/metadata-index.mjs +495 -0
- package/engine/note-claims.mjs +146 -25
- package/engine/note-ids.mjs +112 -0
- package/engine/note-renames.mjs +134 -0
- package/engine/note-schemas.mjs +25 -0
- package/engine/note-vocabulary.mjs +110 -27
- package/engine/pack-config.mjs +4 -0
- package/engine/pack-router.mjs +23 -0
- package/engine/retired-fields.mjs +137 -3
- package/engine/scenes.mjs +66 -34
- package/engine/schema-check.mjs +58 -17
- package/engine/site-build.mjs +71 -12
- package/engine/site-index.mjs +20 -7
- package/engine/sql-tables.mjs +485 -0
- package/engine/subtype-registry.mjs +102 -0
- package/engine/system-block.mjs +96 -12
- package/engine/systems.mjs +201 -0
- package/engine/web-wikilinks.mjs +50 -20
- package/engine/wikilink-syntax.mjs +17 -8
- package/engine/wikilinks.mjs +124 -51
- package/engine/yaml-lint.mjs +282 -0
- package/hm3/actors.mjs +352 -0
- package/hm3/default-item-art.mjs +75 -0
- package/hm3/document-subtypes.mjs +134 -0
- package/hm3/index.mjs +56 -0
- package/hm3/item-builders.mjs +84 -0
- package/hm3/item-fields.mjs +180 -0
- package/hm3/items.mjs +96 -0
- package/hm3/template-priority.mjs +85 -0
- package/labels.mjs +191 -0
- package/manifest.mjs +43 -2
- package/package.json +19 -4
- package/release.mjs +62 -7
- package/sohl/actors.mjs +33 -487
- package/sohl/being-info.mjs +16 -7
- package/sohl/default-item-art.mjs +14 -3
- package/sohl/document-subtypes.mjs +16 -10
- package/sohl/item-builders.mjs +14 -5
- package/sohl/item-fields.mjs +68 -7
- package/sohl/items.mjs +44 -258
- package/sohl/note-schemas.mjs +2 -2
- package/types/content-config.d.mts +21 -42
- package/types/engine/actor-compiler.d.mts +204 -0
- package/types/engine/address-charset.d.mts +11 -8
- package/types/engine/address-diff.d.mts +53 -5
- package/types/engine/anchored-sections.d.mts +21 -0
- package/types/engine/anchors.d.mts +20 -0
- package/types/engine/base-compiler.d.mts +17 -17
- package/types/engine/bundle-notes.d.mts +173 -0
- package/types/engine/bundles.d.mts +60 -0
- package/types/engine/code-fences.d.mts +43 -0
- package/types/engine/compile-corpus.d.mts +32 -0
- package/types/engine/content-address.d.mts +205 -5
- package/types/engine/content-format-check.d.mts +6 -2
- package/types/engine/content-format.d.mts +57 -1
- package/types/engine/content-index.d.mts +64 -80
- package/types/engine/content-links.d.mts +52 -7
- package/types/engine/content-lint.d.mts +10 -1
- package/types/engine/content-package.d.mts +2 -1
- package/types/engine/content-tables.d.mts +21 -39
- package/types/engine/document-subtypes.d.mts +37 -3
- package/types/engine/field-spec.d.mts +76 -5
- package/types/engine/folder-notes.d.mts +159 -0
- package/types/engine/foreign-catalog.d.mts +53 -0
- package/types/engine/{manifest-emit.d.mts → foundry-entries.d.mts} +16 -38
- package/types/engine/frontmatter-lint.d.mts +10 -2
- package/types/engine/frontmatter.d.mts +64 -0
- package/types/engine/generate.d.mts +38 -0
- package/types/engine/helpers.d.mts +94 -30
- package/types/engine/ids.d.mts +96 -0
- package/types/engine/index-records.d.mts +68 -0
- package/types/engine/index.d.mts +9 -3
- package/types/engine/item-compiler.d.mts +131 -0
- package/types/engine/journals.d.mts +47 -9
- package/types/engine/metadata-index.d.mts +226 -0
- package/types/engine/note-claims.d.mts +57 -11
- package/types/engine/note-ids.d.mts +38 -0
- package/types/engine/note-renames.d.mts +102 -0
- package/types/engine/note-vocabulary.d.mts +44 -8
- package/types/engine/retired-fields.d.mts +75 -0
- package/types/engine/scenes.d.mts +3 -2
- package/types/engine/schema-check.d.mts +25 -4
- package/types/engine/site-build.d.mts +4 -4
- package/types/engine/site-index.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +185 -0
- package/types/engine/subtype-registry.d.mts +49 -0
- package/types/engine/system-block.d.mts +40 -1
- package/types/engine/systems.d.mts +106 -0
- package/types/engine/web-wikilinks.d.mts +4 -2
- package/types/engine/wikilink-syntax.d.mts +10 -3
- package/types/engine/wikilinks.d.mts +41 -13
- package/types/engine/yaml-lint.d.mts +107 -0
- package/types/hm3/actors.d.mts +48 -0
- package/types/hm3/default-item-art.d.mts +42 -0
- package/types/hm3/document-subtypes.d.mts +24 -0
- package/types/hm3/index.d.mts +7 -0
- package/types/hm3/item-builders.d.mts +11 -0
- package/types/hm3/item-fields.d.mts +12 -0
- package/types/hm3/items.d.mts +23 -0
- package/types/hm3/template-priority.d.mts +21 -0
- package/types/manifest.d.mts +22 -1
- package/types/release.d.mts +9 -4
- package/types/sohl/actors.d.mts +7 -74
- package/types/sohl/being-info.d.mts +8 -27
- package/types/sohl/default-item-art.d.mts +5 -3
- package/types/sohl/items.d.mts +17 -32
- package/engine/foreign-manifests.mjs +0 -126
- package/engine/kb-manifest.mjs +0 -490
- package/types/engine/foreign-manifests.d.mts +0 -43
- package/types/engine/kb-manifest.d.mts +0 -241
package/CONTENT.md
CHANGED
|
@@ -22,7 +22,7 @@ A consuming repository declares one `package-build.config.yaml` at its root:
|
|
|
22
22
|
|
|
23
23
|
```yaml
|
|
24
24
|
# The package this repository's content is published as: the first segment of
|
|
25
|
-
# every canonical address, the name of the
|
|
25
|
+
# every canonical address, the name of the content index it emits, and the
|
|
26
26
|
# package a cross-package wikilink writes to reach one of its notes.
|
|
27
27
|
contentPackage: thalorna
|
|
28
28
|
# Where Foundry installs it: "systems" or "modules". Also decides the served
|
|
@@ -186,11 +186,27 @@ something migrates on it.
|
|
|
186
186
|
### A note's package is the repository's, not the note's
|
|
187
187
|
|
|
188
188
|
`contentPackage` is the **address namespace** every note in the tree is
|
|
189
|
-
published under: the first segment of every canonical key
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
published under: the first segment of every canonical key, the name of the link
|
|
190
|
+
manifest this build emits (`sohl.json`), and the package a cross-package
|
|
191
|
+
wikilink writes to reach one of these notes. It is the repository's identity in
|
|
192
|
+
the address space — not a filter — and a note does not restate it.
|
|
193
|
+
|
|
194
|
+
A canonical key has four segments, read by position:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
<package>-<system>-<type>-<shortcode>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`<system>` is a game system this toolchain compiles for — `sohl` or `hm3` — or
|
|
201
|
+
the literal `none` for a document no game system defines: a JournalEntry, a
|
|
202
|
+
Macro, a Scene, and an item's documentation journal, which is `none` however
|
|
203
|
+
many system blocks the item itself carries. The registry of permitted values is
|
|
204
|
+
`engine/systems.mjs`, and `none` is a **word** on purpose: `any` would read as a
|
|
205
|
+
wildcard, which is the opposite of what it says, and a YAML null (`null`, `~`,
|
|
206
|
+
an empty value) parses to an absent value and drops the segment altogether. So
|
|
207
|
+
`sohl-none-doc-gear`, `sohl-sohl-skill-clmb` — the package and the system are
|
|
208
|
+
independent slots that may hold the same word, because Foundry requires a system
|
|
209
|
+
package's id to _be_ its system id.
|
|
194
210
|
|
|
195
211
|
Because it is a segment of an address, the value is **validated** rather than
|
|
196
212
|
taken as written, and a violation fails the build naming the line it is on:
|
|
@@ -199,14 +215,20 @@ taken as written, and a violation fails the build naming the line it is on:
|
|
|
199
215
|
hyphen-separated segments, so the hyphen has to be purely a separator — which
|
|
200
216
|
is why `harn-adventures` is configured as `harnadventures`. This is the same
|
|
201
217
|
rule `shortcode` is already held to, and the two are one constant.
|
|
202
|
-
- **Not a note type.**
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
- **Not a note type.** A written address is a _partial_ one — the shorter forms
|
|
219
|
+
drop segments from the left, so `skill-clmb` and `sohl-skill-clmb` are both
|
|
220
|
+
addresses — and position alone therefore no longer says which vocabulary a
|
|
221
|
+
leading segment is drawn from. The reader settles that by asking whether the
|
|
222
|
+
name is a known package, so a name belonging to both vocabularies makes one
|
|
223
|
+
target readable two ways with no defensible pick. The package and the type are
|
|
224
|
+
no longer _adjacent_ segments now that the system sits between them, and that
|
|
225
|
+
changes nothing: the hazard was never adjacency, it is that a short form omits
|
|
226
|
+
the slots in between. `doc`, `being`, every map type, and every item type this
|
|
205
227
|
repository declares — with its `doc`-prefixed documentation form — are
|
|
206
228
|
refused.
|
|
207
229
|
|
|
208
230
|
```text
|
|
209
|
-
package-build.config.yaml:1:1: error: package-build config: `contentPackage` is `harn-adventures`, which is not alphanumeric. It is the first segment of every address this package publishes (`harn-adventures-<type>-<shortcode>`), and an address is read by counting hyphen-separated segments — so anything outside `[A-Za-z0-9]` here makes those addresses unreadable rather than merely ugly. `harn-adventures` became `harnadventures`.
|
|
231
|
+
package-build.config.yaml:1:1: error: package-build config: `contentPackage` is `harn-adventures`, which is not alphanumeric. It is the first segment of every address this package publishes (`harn-adventures-<system>-<type>-<shortcode>`), and an address is read by counting hyphen-separated segments — so anything outside `[A-Za-z0-9]` here makes those addresses unreadable rather than merely ugly. `harn-adventures` became `harnadventures`.
|
|
210
232
|
```
|
|
211
233
|
|
|
212
234
|
**`package:` in a note's frontmatter is retired, and declaring it fails the
|
|
@@ -220,7 +242,7 @@ assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter
|
|
|
220
242
|
```
|
|
221
243
|
|
|
222
244
|
`content-build lint` reports every such note in one pass; `content-build
|
|
223
|
-
package compile` and `content-build
|
|
245
|
+
package compile` and `content-build content-index` refuse the tree.
|
|
224
246
|
|
|
225
247
|
A generated table that scopes itself with `WHERE … and package = "<pkg>"` keeps
|
|
226
248
|
working: the package is **synthesised** into what the table search sees,
|
|
@@ -400,6 +422,41 @@ system)` take the system that is asking; asking without one, for a type more
|
|
|
400
422
|
than one registry declares, **throws** rather than answering with whichever was
|
|
401
423
|
declared first.
|
|
402
424
|
|
|
425
|
+
**`hm3` is a real registry, and the rest of the pipeline follows the same
|
|
426
|
+
field.** Since #139 this package ships two system halves, `sohl/` and `hm3/`,
|
|
427
|
+
each with its own builders, its own default art and its own note-type →
|
|
428
|
+
document-subtype map; they share the engine between them and import nothing from
|
|
429
|
+
each other. A pack's `system:` is what selects among them — the Item and Actor
|
|
430
|
+
compilers, the item catalogue a being resolves against, the published
|
|
431
|
+
`schema.json` its emissions are checked against, and the `_stats` stamp all read
|
|
432
|
+
that one field — so declaring one Item pack and one Actor pack per system is the
|
|
433
|
+
whole of the configuration a dual-system tree needs:
|
|
434
|
+
|
|
435
|
+
```yaml
|
|
436
|
+
itemBuilders: [sohl, hm3]
|
|
437
|
+
systems:
|
|
438
|
+
sohl: { compatibility: { verified: "0.9.0" } }
|
|
439
|
+
hm3: { compatibility: { verified: "1.6.3" } }
|
|
440
|
+
packs:
|
|
441
|
+
- { name: items-sohl, type: Item, system: sohl, default: true }
|
|
442
|
+
- { name: items-hm3, type: Item, system: hm3 }
|
|
443
|
+
- { name: actors-sohl, type: Actor, system: sohl, default: true }
|
|
444
|
+
- { name: actors-hm3, type: Actor, system: hm3 }
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
A note carrying both a `sohl:` and an `hm3:` block then compiles **one document
|
|
448
|
+
in each system**, each shaped by its own builders and stamped with its own
|
|
449
|
+
system version. A note carrying only one block compiles only that system's
|
|
450
|
+
document: the other system's pass passes over it, rather than failing it for a
|
|
451
|
+
block it was never going to have.
|
|
452
|
+
|
|
453
|
+
Four of HM3's rows are **one-to-many** — `mysticalability` becomes a `psionic`,
|
|
454
|
+
a `spell` or an `invocation`; `trauma` an `injury` or a `trait`; `weapongear` a
|
|
455
|
+
`weapongear` or a `missilegear`; `being` a `character` or a `creature` — and the
|
|
456
|
+
note says which by writing `hm3.type`. Nothing is inferred from the note's own
|
|
457
|
+
`subType`, and a note that says nothing is an error naming the note and listing
|
|
458
|
+
the permitted values.
|
|
459
|
+
|
|
403
460
|
**Configuration is the source, and the manifest is generated from it.** That
|
|
404
461
|
arrow used to point the other way: `paths.packageManifest` said where a
|
|
405
462
|
hand-authored `system.template.json` lived, and the package-id guard and the
|
|
@@ -584,16 +641,24 @@ toolchain vocabulary, and the _generators_ `items` and `attributes` expand into
|
|
|
584
641
|
embedded documents rather than mapping anywhere, so neither has a `system` path
|
|
585
642
|
to be written at.
|
|
586
643
|
|
|
587
|
-
`
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
`
|
|
591
|
-
is
|
|
644
|
+
`templatePriority` is the one key of this shape that _is_ a field, and it is
|
|
645
|
+
**shared rather than per-system**: its home is `data.templatePriority`, and it
|
|
646
|
+
reaches `sohl.system.templatePriority` and `hm3.flags.hm3.templatePriority` —
|
|
647
|
+
exactly as `portrait` reaches two differently-named fields from one shared
|
|
648
|
+
property. A number is a template at that priority, `null` is not a template, and
|
|
649
|
+
absent is an authoring error (#126, #266). The legacy in-block and top-level
|
|
650
|
+
positions are still read, in that order after `data:`, so a tree sweeps on its
|
|
651
|
+
own schedule; `archetype` is the retiring spelling of the same field, still read
|
|
652
|
+
last but **refused by the frontmatter linter** — a priority and the `archetypes`
|
|
653
|
+
a being fits are different things, and one letter is not enough to tell them
|
|
654
|
+
apart.
|
|
592
655
|
|
|
593
656
|
```yaml
|
|
594
657
|
type: being # the content type — system-agnostic
|
|
595
658
|
pack: actors # shared: unless a block says otherwise
|
|
596
659
|
portrait: kaldor.webp # shared: reaches both systems' fields, differently named
|
|
660
|
+
data:
|
|
661
|
+
templatePriority: 1 # shared: → sohl `system.`, hm3 `flags.hm3.`
|
|
597
662
|
hm3:
|
|
598
663
|
type: character # this system's document subtype
|
|
599
664
|
pack: actors-hm3 # overrides the shared one, for HM3 only
|
|
@@ -605,7 +670,6 @@ sohl:
|
|
|
605
670
|
type: being
|
|
606
671
|
system:
|
|
607
672
|
currentMoveMedium: walk
|
|
608
|
-
archetype: 1
|
|
609
673
|
```
|
|
610
674
|
|
|
611
675
|
**The shared fallback is declared, not name-matched.** `sohl.system.portrait`
|
|
@@ -676,7 +740,7 @@ value goes through one coercion rather than two.
|
|
|
676
740
|
|
|
677
741
|
**What the compiler writes on its own is checked too.** A compiled document
|
|
678
742
|
carries keys no field declaration and no note ever names — `shortcode`,
|
|
679
|
-
`
|
|
743
|
+
`templatePriority`, `actionDefs`, `notes`, `docHtml` — because the pass writes them
|
|
680
744
|
itself, and they were compared against nothing: the declaration-derived check
|
|
681
745
|
reads `itemBuilders`, the note-side check reads `<system>.system`, and these are
|
|
682
746
|
in neither. A compile now reads the `system` block each pass **assembled** and
|
|
@@ -715,7 +779,6 @@ npx content-build content-format notes [root] [--strict]
|
|
|
715
779
|
npx content-build links [root] [--manifests <dir>]
|
|
716
780
|
npx content-build format [paths..] [--write]
|
|
717
781
|
npx content-build markdown [paths..] [--fix]
|
|
718
|
-
npx content-build manifest [root] [--out <dir>]
|
|
719
782
|
npx content-build content-index [root] [--out <dir>]
|
|
720
783
|
npx content-build site [--out <dir>]
|
|
721
784
|
npx content-build reachability <dir> [file] [--index <shortcode>]
|
|
@@ -731,7 +794,6 @@ npx content-build addresses diff --from <zip|dir> [--strict]
|
|
|
731
794
|
| `links` | Check that every link in the tree lands: dead anchors, dead qualified addresses, wikilinks in frontmatter, drifted manifests, and the package homepage's own addresses. |
|
|
732
795
|
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown). |
|
|
733
796
|
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to. |
|
|
734
|
-
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest). |
|
|
735
797
|
| `content-index` | Emit this package's note index as JSON Lines. See [Publishing a content index](#publishing-a-content-index). |
|
|
736
798
|
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website). |
|
|
737
799
|
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point. |
|
|
@@ -894,10 +956,11 @@ mode rather than by kind — so `subType` on one is a finding; a `skill` declare
|
|
|
894
956
|
ten, so `subType: crafte` is a finding naming `craft`.
|
|
895
957
|
|
|
896
958
|
**A `type` and a `subType` are both held to `^[A-Za-z0-9]+$`** (#206) — the same
|
|
897
|
-
constant a `shortcode` is held to, read rather than restated. A type is
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
959
|
+
constant a `shortcode` is held to, read rather than restated. A type is a
|
|
960
|
+
segment of every address — the first of the short form an author writes, the
|
|
961
|
+
third of the canonical `package-system-type-shortcode` — so a hyphen in one is
|
|
962
|
+
read back as a segment boundary nobody wrote. A `subType` reaches no address
|
|
963
|
+
since #204 retired sections, and keeps the rule anyway: it is a vocabulary term the toolchain keys
|
|
901
964
|
on, and one charset that holds for every term is a rule an author can state. The
|
|
902
965
|
rule is checked ahead of the closed-set check, which is what makes it reach a
|
|
903
966
|
type whose values are declared but not yet enumerated:
|
|
@@ -935,6 +998,43 @@ folded in every note's `name.full` and so decided what a note could be named
|
|
|
935
998
|
`[[type-shortcode|Text]]`, and declaring `aliases:` is refused naming the file
|
|
936
999
|
and the line.
|
|
937
1000
|
|
|
1001
|
+
### Writing a link: the address grammar
|
|
1002
|
+
|
|
1003
|
+
Every wikilink is an address, and omission runs **strictly left to right**:
|
|
1004
|
+
|
|
1005
|
+
```text
|
|
1006
|
+
[[[[<package>-]<system>-]<type>-]<shortcode>]
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
So the written forms are exactly the suffixes of the canonical address:
|
|
1010
|
+
|
|
1011
|
+
| Form | Example | Means |
|
|
1012
|
+
| ------------------------------- | ------------------------------------ | --------------------------------- |
|
|
1013
|
+
| `type-shortcode` | `[[skill-clmb\|Climbing]]` | This package, any system. |
|
|
1014
|
+
| `system-type-shortcode` | `[[sohl-skill-clmb\|Climbing]]` | This package, the `sohl` system. |
|
|
1015
|
+
| `package-system-type-shortcode` | `[[thalorna-sohl-being-grod\|Grod]]` | Another package, fully qualified. |
|
|
1016
|
+
|
|
1017
|
+
**There is no `package-type-shortcode`.** Naming a package means naming the
|
|
1018
|
+
system before the type, because the segments are positional rather than tagged.
|
|
1019
|
+
A link into another package must therefore be fully qualified — which is the
|
|
1020
|
+
price of a grammar that needs no vocabulary to parse.
|
|
1021
|
+
|
|
1022
|
+
**An omitted system is a wildcard; an omitted package is a default.** Most links
|
|
1023
|
+
target items, which belong to a system, so a target naming none matches a note
|
|
1024
|
+
under any of them and exactly one hit is required — two claimants is an
|
|
1025
|
+
_ambiguity_, a different finding with a different fix from resolving nowhere. A
|
|
1026
|
+
target naming no package means the citing note's own, so an unqualified link
|
|
1027
|
+
resolves locally and only locally.
|
|
1028
|
+
|
|
1029
|
+
**`sohl` is both a package and a system**, and positional counting is what makes
|
|
1030
|
+
that harmless: three segments name a _system_ whatever the first segment could
|
|
1031
|
+
also have meant, and four is the full form.
|
|
1032
|
+
|
|
1033
|
+
**Parsing is plain segment counting**, the same rule the canonical key follows,
|
|
1034
|
+
and it is sound because every segment is `^[A-Za-z0-9]+$` — so a hyphen is
|
|
1035
|
+
purely a separator. A target with five segments is not a hyphenated shortcode;
|
|
1036
|
+
it is a name, and not an address.
|
|
1037
|
+
|
|
938
1038
|
**`name.aliases` is kept, and is read by nothing.** It fed the same index and
|
|
939
1039
|
lost the same reader, but unlike the top-level list it is **reserved** — held
|
|
940
1040
|
for a use that does not exist yet. So it is the one field in the format that is
|
|
@@ -988,10 +1088,10 @@ the Foundry document id a compendium UUID is built from, and a homepage compiles
|
|
|
988
1088
|
into **no document**.
|
|
989
1089
|
|
|
990
1090
|
```text
|
|
991
|
-
assets/content/homepage.md:4:1: error: `id` decides nothing on a `type: homepage` note: it is the Foundry document id a compendium UUID is built from, and a homepage compiles into no document — it appears in no pack and
|
|
1091
|
+
assets/content/homepage.md:4:1: error: `id` decides nothing on a `type: homepage` note: it is the Foundry document id a compendium UUID is built from, and a homepage compiles into no document — it appears in no pack and states no Foundry address. Delete it
|
|
992
1092
|
```
|
|
993
1093
|
|
|
994
|
-
That is also why a homepage
|
|
1094
|
+
That is also why a homepage states **no Foundry address**, now that a
|
|
995
1095
|
shortcode alone would put it in. A manifest entry is how another package
|
|
996
1096
|
resolves a _document_; a cross-package link to a package's front page is its
|
|
997
1097
|
bare `/<package>/` address, which needs no index.
|
|
@@ -1128,7 +1228,7 @@ attempted:
|
|
|
1128
1228
|
documentation, hand-authored Hugo sections — so this build does not hold the
|
|
1129
1229
|
set of published pages and would report a working link as dead. A bare
|
|
1130
1230
|
`https://www.heroiclands.org/<package>/` is left alone for the same reason it
|
|
1131
|
-
cannot be improved: a package homepage
|
|
1231
|
+
cannot be improved: a package homepage compiles into no document, so there is no
|
|
1132
1232
|
better form to write.
|
|
1133
1233
|
|
|
1134
1234
|
## The content format specification
|
|
@@ -1260,44 +1360,145 @@ import shared from "@heroiclands/package-build/markdownlint";
|
|
|
1260
1360
|
export default { ...shared, config: { ...shared.config, MD013: true } };
|
|
1261
1361
|
```
|
|
1262
1362
|
|
|
1263
|
-
##
|
|
1363
|
+
## YAML: frontmatter, and every YAML file
|
|
1364
|
+
|
|
1365
|
+
```bash
|
|
1366
|
+
npx package-build yaml # every YAML file git would consider
|
|
1367
|
+
npx package-build yaml assets/content # or just these paths
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
Frontmatter carries a note's type, its shortcode, its address and the system
|
|
1371
|
+
blocks a document is compiled from — and until this command existed nothing
|
|
1372
|
+
checked it _as YAML_.
|
|
1373
|
+
|
|
1374
|
+
**Worse than unchecked: a parse failure unmade the note.** `parseMarkdownFile`
|
|
1375
|
+
caught the error, logged it at `warn`, and returned `{frontmatter: null}` — which
|
|
1376
|
+
is not a note with bad frontmatter but, to every pass downstream, _a file with no
|
|
1377
|
+
frontmatter_. It was skipped by the compiler, the linter, the link checker and
|
|
1378
|
+
the index, and the build reported success. A duplicate key did not fail anything;
|
|
1379
|
+
it removed a note from the corpus. The parser had detected it all along.
|
|
1380
|
+
|
|
1381
|
+
**Frontmatter is linted through an ESLint processor** — the mechanism
|
|
1382
|
+
`eslint-plugin-markdown` uses for fenced code blocks. Frontmatter is its easy
|
|
1383
|
+
case: the block is always at the top of the file, so a finding maps back to the
|
|
1384
|
+
line it came from with a constant `+1` for the opening `---`, and nothing after
|
|
1385
|
+
the closing `---` is read as YAML.
|
|
1386
|
+
|
|
1387
|
+
The rule set is **deliberately narrow**, for the same reason `markdown`'s is.
|
|
1388
|
+
Prettier already owns YAML's whitespace, quoting and line breaks — including
|
|
1389
|
+
inside a frontmatter fence — so a rule about any of those would duplicate the
|
|
1390
|
+
formatter or fight it. What is left is the class a formatter cannot see: text
|
|
1391
|
+
that parses to something other than what it looks like.
|
|
1392
|
+
|
|
1393
|
+
| Reported | Why it is not a matter of taste |
|
|
1394
|
+
| ------------------------------------- | -------------------------------------------------------------- |
|
|
1395
|
+
| a parse error | A duplicate key, a tab indent, mis-aligned mapping items. |
|
|
1396
|
+
| `yml/no-empty-mapping-value` | `folder:` and `folder: null` are one value and two statements. |
|
|
1397
|
+
| `yml/no-irregular-whitespace` | A non-breaking space is invisible and part of the value. |
|
|
1398
|
+
| `yml/no-empty-key`, `-empty-document` | A fence or a file that parses to nothing at all. |
|
|
1399
|
+
|
|
1400
|
+
`folder:` and `folder: null` read as opposites — a decision, or a key somebody
|
|
1401
|
+
began and did not finish — so the distinction is drawn where the text still
|
|
1402
|
+
exists. **A key with a block under it is not empty**: `name:` followed by an
|
|
1403
|
+
indented mapping, or by a sequence at its own indent, is an ordinary container.
|
|
1404
|
+
|
|
1405
|
+
**GitHub workflows are exempt from the empty-value rule.** `on:`, `push:` and
|
|
1406
|
+
`workflow_dispatch:` carry their meaning by being present, and writing
|
|
1407
|
+
`push: null` to satisfy a linter would be worse YAML, not better. A real error in
|
|
1408
|
+
a workflow is still reported.
|
|
1409
|
+
|
|
1410
|
+
**The files are the ones git would consider** — `--cached --others
|
|
1411
|
+
--exclude-standard`, tracked plus untracked-and-not-ignored, the same set
|
|
1412
|
+
`gitignore: true` gives the markdown linter. Untracked is included so a note is
|
|
1413
|
+
linted while it is being written rather than only once it has been staged.
|
|
1414
|
+
|
|
1415
|
+
**A consumer needs no ESLint.** This ships as a command, not as a configuration
|
|
1416
|
+
to adopt: there is no `eslint` dependency to add, no `eslint.config.js` and no
|
|
1417
|
+
rules to declare. A repository that _has_ an ESLint of its own keeps it untouched
|
|
1418
|
+
and unconsulted — the run sets `overrideConfigFile: true`, so no config file is
|
|
1419
|
+
looked for at all.
|
|
1420
|
+
|
|
1421
|
+
## Publishing the content index
|
|
1422
|
+
|
|
1423
|
+
Every package emits one file naming every note it publishes:
|
|
1264
1424
|
|
|
1265
1425
|
```bash
|
|
1266
|
-
npx content-build
|
|
1267
|
-
npx content-build manifest --out tmp/ # or somewhere else
|
|
1426
|
+
npx content-build content-index # the configured tree and output directory
|
|
1268
1427
|
```
|
|
1269
1428
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
note has:
|
|
1273
|
-
sections compiled to, and a `
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
|
1282
|
-
|
|
|
1283
|
-
| `
|
|
1284
|
-
| `
|
|
1285
|
-
| `paths.
|
|
1286
|
-
| `
|
|
1287
|
-
| `publish.site` | Whether entries carry a `path` — see below. |
|
|
1288
|
-
| `publish.address` | The address scheme those paths are derived under — see below. |
|
|
1429
|
+
`<contentPackage>-metadata.jsonl` holds one JSON record per line, keyed by the
|
|
1430
|
+
canonical `package-system-type-shortcode` address and carrying every address
|
|
1431
|
+
that note has: its `address.slug` on the web, a `foundry.<system>.uuid` in
|
|
1432
|
+
Foundry, the `anchors` its named sections compiled to, and a `documentation`
|
|
1433
|
+
pointer where an item's prose compiles into a JournalEntry of its own.
|
|
1434
|
+
|
|
1435
|
+
**This is the artifact other packages resolve your addresses through.** It is
|
|
1436
|
+
advertised in the emitted `system.json` / `module.json` as
|
|
1437
|
+
`flags.metadataUrl` — derived, version-pinned, and not something you write down
|
|
1438
|
+
— and published as a release asset beside the manifest and the `.zip`.
|
|
1439
|
+
|
|
1440
|
+
| Setting | What it decides |
|
|
1441
|
+
| -------------------- | --------------------------------------------- |
|
|
1442
|
+
| `contentPackage` | The package emitted, and the file's name. |
|
|
1443
|
+
| `foundryPackage` | The package every emitted `uuid` names. |
|
|
1444
|
+
| `paths.content` | The tree walked. |
|
|
1445
|
+
| `paths.contentIndex` | Where the file lands (`build/content-index`). |
|
|
1289
1446
|
|
|
1290
1447
|
**Both addresses are optional, independently.** A note that compiles into no
|
|
1291
|
-
document
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1448
|
+
document states no `foundry` block, and a package that ships compendiums and
|
|
1449
|
+
publishes only a homepage serves no page at its addresses. Neither is an error,
|
|
1450
|
+
and neither is guessed: inventing the missing one asserts a target that does not
|
|
1451
|
+
exist, which is the silent dead link this whole mechanism exists to prevent.
|
|
1452
|
+
|
|
1453
|
+
## Resolving another package's addresses
|
|
1454
|
+
|
|
1455
|
+
Declare what you depend on, and fetch it:
|
|
1456
|
+
|
|
1457
|
+
```bash
|
|
1458
|
+
npx content-build deps fetch # every declared dependency
|
|
1459
|
+
npx content-build deps fetch --from ../sohl # from a local build, unreleased
|
|
1460
|
+
```
|
|
1296
1461
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1462
|
+
```yaml
|
|
1463
|
+
relationships:
|
|
1464
|
+
systems:
|
|
1465
|
+
- id: sohl
|
|
1466
|
+
type: system
|
|
1467
|
+
manifest: https://github.com/HeroicLands/Song-of-Heroic-Lands-FoundryVTT/releases/latest/download/system.json
|
|
1468
|
+
compatibility: { minimum: "0.8.0", verified: "0.8.2" }
|
|
1469
|
+
```
|
|
1470
|
+
|
|
1471
|
+
`deps fetch` reads that manifest, takes the `flags.metadataUrl` it advertises,
|
|
1472
|
+
and pulls the index into `build/cache/metadata`. The whole chain is declared, so
|
|
1473
|
+
nothing here holds an address of its own and a dependency that moves its release
|
|
1474
|
+
assets does not break its consumers.
|
|
1475
|
+
|
|
1476
|
+
**You may cite what you depend on, and nothing else.** The set is every entry in
|
|
1477
|
+
`relationships.systems` and `relationships.requires`; `recommends` and
|
|
1478
|
+
`conflicts` are declarations _about_ other packages rather than dependencies on
|
|
1479
|
+
them, so a link into one is a defect in the citing note. An address into a
|
|
1480
|
+
package you have not declared resolves nowhere and fails the build.
|
|
1481
|
+
|
|
1482
|
+
**A build never reaches the network.** A declared dependency whose index has not
|
|
1483
|
+
been fetched is an error naming `deps fetch`, rather than a download nobody
|
|
1484
|
+
asked for — a build that downloads silently is not reproducible and fails
|
|
1485
|
+
strangely offline. The cache is keyed by version, so changing the pinned version
|
|
1486
|
+
is a miss rather than a silent overwrite.
|
|
1487
|
+
|
|
1488
|
+
**`--from` is for two packages changing together.** It fills the cache from a
|
|
1489
|
+
locally built artifact — a package zip or the directory it was built from — so a
|
|
1490
|
+
consumer can be built against a dependency that has not shipped. Without it,
|
|
1491
|
+
testing a dependency change against its consumers would cost a release
|
|
1492
|
+
round-trip.
|
|
1493
|
+
|
|
1494
|
+
> **This replaced a vendored link manifest**, which every repository committed a
|
|
1495
|
+
> copy of every other repository's file into. A copy went stale silently and one
|
|
1496
|
+
> did — 2,101 entries in `Song-of-Heroic-Lands-FoundryVTT` pointed at URLs the
|
|
1497
|
+
> `thalorna` site had stopped publishing, at the current format version, so the
|
|
1498
|
+
> version gate saw nothing. Mutual vendoring also deadlocked: each package had to
|
|
1499
|
+
> read the other's file before it could publish its own. A fetched artifact
|
|
1500
|
+
> cannot drift from its producer, and a consumer reads one the producer has
|
|
1501
|
+
> already shipped.
|
|
1301
1502
|
|
|
1302
1503
|
### A page's URL is its address
|
|
1303
1504
|
|
|
@@ -1310,6 +1511,14 @@ writing.
|
|
|
1310
1511
|
is no collision check behind it, there never can be one to fail, and renaming a
|
|
1311
1512
|
note changes nothing: no part of the address comes from a display string.
|
|
1312
1513
|
|
|
1514
|
+
**A URL carries no `<system>` segment**, though the canonical address does. That
|
|
1515
|
+
is deliberate rather than an omission: a note publishes one page however many
|
|
1516
|
+
systems' documents it compiles into, so the segment would have nothing to
|
|
1517
|
+
distinguish and would only split one page's URL in two. The canonical address
|
|
1518
|
+
names a _document_; a URL names a _page_. So a consumer deriving a page address
|
|
1519
|
+
from a manifest key drops the package **and** the system, not the package
|
|
1520
|
+
alone.
|
|
1521
|
+
|
|
1313
1522
|
It used to come from `name.full`. That made a display name load-bearing three
|
|
1314
1523
|
ways at once — a rename silently 404'd every inbound link, two notes in one
|
|
1315
1524
|
section could derive the same URL so a uniqueness gate had to run, and long names
|
|
@@ -1462,7 +1671,7 @@ A record states the address a wikilink writes to reach the note, and every
|
|
|
1462
1671
|
|
|
1463
1672
|
```json
|
|
1464
1673
|
{
|
|
1465
|
-
"address": { "slug": "being-aurochs", "canonical": "sohl-being-aurochs" },
|
|
1674
|
+
"address": { "slug": "being-aurochs", "canonical": "sohl-sohl-being-aurochs" },
|
|
1466
1675
|
"file": { "path": "Bestiary/Animal/Aurochs.md", "folder": "Bestiary/Animal", "name": "Aurochs" },
|
|
1467
1676
|
"anchors": [
|
|
1468
1677
|
{
|
|
@@ -1524,18 +1733,22 @@ first. An entry that is not a non-empty string is dropped rather than left as a
|
|
|
1524
1733
|
hole, since the array is a set of names to match and a null is not one.
|
|
1525
1734
|
|
|
1526
1735
|
`address.slug` is what goes inside `[[…]]` within the package; `address.canonical`
|
|
1527
|
-
is the
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1736
|
+
is the fully qualified key the content index files the note under, carrying the
|
|
1737
|
+
package and the system as well. The slug is the canonical key's **last two
|
|
1738
|
+
segments**, not its whole tail: a page has no system to name, so the two forms
|
|
1739
|
+
diverge by that segment rather than one trailing the other. Both are `null` for a
|
|
1740
|
+
note with no type or no shortcode, which has no address at all — the record says
|
|
1741
|
+
so rather than leaving each reader to rediscover the rule.
|
|
1742
|
+
|
|
1743
|
+
**Neither is new information** — the slug derives from `type` and `shortcode`,
|
|
1744
|
+
which every record already carries, and the canonical key adds only the system
|
|
1745
|
+
those two already imply. What the fields add is the _rule_: the lowercasing
|
|
1533
1746
|
and the hyphen join live in one place, derived by the same `addressSlug` and
|
|
1534
1747
|
`canonicalKey` the manifest and the site build use, so an index cannot disagree
|
|
1535
1748
|
with either about where a note lives. A consumer that reimplements the join
|
|
1536
1749
|
slightly differently gets a lookup matching nothing and no explanation — which is
|
|
1537
1750
|
exactly how a resolver keyed on a bare `type/shortcode` silently misses every
|
|
1538
|
-
canonical `pkg-type-shortcode` entry.
|
|
1751
|
+
canonical `pkg-system-type-shortcode` entry.
|
|
1539
1752
|
|
|
1540
1753
|
**Anchors make a link checkable without a build.** Because the index states every
|
|
1541
1754
|
anchor a note defines, `[[being-aurochs#dossier]]` can be confirmed — or shown
|
|
@@ -1666,9 +1879,12 @@ configuration to stay empty: in `homepage` mode the tree is never walked for
|
|
|
1666
1879
|
pages, and `sections`, `trees`, `landing` and `backfillSections` emit nothing
|
|
1667
1880
|
even when they are declared.
|
|
1668
1881
|
|
|
1669
|
-
That is separate from
|
|
1670
|
-
|
|
1671
|
-
|
|
1882
|
+
That is separate from the **dependency** edge, which such a module also
|
|
1883
|
+
declines: being cited by another package is what would stop it being
|
|
1884
|
+
withdrawable, and a homepage is one row in a routing table rather than an
|
|
1885
|
+
address anyone links to. A package publishes its content index regardless — the
|
|
1886
|
+
licensing constraint is against publishing _pages_, not against the artifact
|
|
1887
|
+
existing — but nothing may declare it as a dependency.
|
|
1672
1888
|
|
|
1673
1889
|
The homepage's file is written at the root of `site.out` — the package's own
|
|
1674
1890
|
site root, one level above the content mount, which is where
|
|
@@ -1678,7 +1894,7 @@ decides where it publishes, and states it relative to the site root — `site.ba
|
|
|
1678
1894
|
does not reach it (#217).
|
|
1679
1895
|
|
|
1680
1896
|
**What it does not do is decide addresses.** Those come from `publish.address`,
|
|
1681
|
-
the same setting the
|
|
1897
|
+
the same setting the content index reads, so a page and its index record cannot
|
|
1682
1898
|
disagree about where the page is. Everything under `site:` is _framing_ —
|
|
1683
1899
|
where the tree is written, what a section is called, which extra trees are
|
|
1684
1900
|
published beside the content:
|
|
@@ -1951,7 +2167,7 @@ form every C-family compiler, `tsc` and ESLint already use, so an editor, a CI
|
|
|
1951
2167
|
annotator or a `grep` parses it with no knowledge of this build:
|
|
1952
2168
|
|
|
1953
2169
|
```text
|
|
1954
|
-
assets/content/Regions/Capital_Nome.md:43:635: error: address [[place-kenbetpat]] resolves to no note — no package publishes it. Fix the shortcode, or
|
|
2170
|
+
assets/content/Regions/Capital_Nome.md:43:635: error: address [[place-kenbetpat]] resolves to no note — no package publishes it. Fix the shortcode, or declare the package that does as a dependency and run `content-build deps fetch` — in "The Capital Nome".
|
|
1955
2171
|
```
|
|
1956
2172
|
|
|
1957
2173
|
`file:line:column: severity: message`. The path is relative to the working
|
|
@@ -2000,7 +2216,7 @@ authored character to point at.
|
|
|
2000
2216
|
## Layout
|
|
2001
2217
|
|
|
2002
2218
|
- **`@heroiclands/package-build/engine`** — package-agnostic machinery: the
|
|
2003
|
-
content walk, frontmatter, tables, wikilinks, ids, folders, the
|
|
2219
|
+
content walk, frontmatter, tables, wikilinks, ids, folders, the content index
|
|
2004
2220
|
and the web-address rule, `BasePackCompiler`, and the generic Foundry document
|
|
2005
2221
|
compilers.
|
|
2006
2222
|
- **`@heroiclands/package-build/sohl`** — Song of Heroic Lands data-model
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ It has two halves, split by **input**:
|
|
|
7
7
|
|
|
8
8
|
| Half | Reads | Produces |
|
|
9
9
|
| ------------- | ------------------------------------------------------------ | -------------------------------------------------------------- |
|
|
10
|
-
| **content** | `assets/content/**` | compendium packs, site content,
|
|
10
|
+
| **content** | `assets/content/**` | compendium packs, site content, content index |
|
|
11
11
|
| **packaging** | `lang/`, `styles/`, `src/`, `assets/`, the manifest template | `system.json` / `module.json`, styles, bundle, release archive |
|
|
12
12
|
|
|
13
13
|
The content half is documented separately in **[CONTENT.md](CONTENT.md)** — the
|