@heroiclands/package-build 5.0.0 → 6.1.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 +503 -0
- package/CONTENT.md +343 -16
- package/MIGRATING.md +41 -0
- package/README.md +30 -0
- package/bin/content-build.mjs +188 -4
- package/bin/package-build.mjs +3 -1
- package/content-config.mjs +29 -7
- package/engine/address-diff.mjs +290 -0
- package/engine/base-compiler.mjs +28 -3
- package/engine/content-links.mjs +221 -2
- package/engine/content-lint.mjs +35 -5
- package/engine/diagnostics.mjs +46 -0
- package/engine/frontmatter-lint.mjs +22 -0
- package/engine/generate.mjs +156 -7
- package/engine/homepage.mjs +315 -2
- package/engine/pack-config.mjs +21 -0
- package/engine/site-build.mjs +84 -23
- package/manifest.mjs +195 -0
- package/package.json +1 -1
- package/sohl/actors.mjs +14 -1
- package/sohl/item-fields.mjs +0 -5
- package/sohl/kb-passes.mjs +81 -14
- package/types/engine/address-diff.d.mts +108 -0
- package/types/engine/base-compiler.d.mts +22 -0
- package/types/engine/content-links.d.mts +53 -2
- package/types/engine/content-lint.d.mts +4 -1
- package/types/engine/diagnostics.d.mts +28 -0
- package/types/engine/generate.d.mts +50 -2
- package/types/engine/homepage.d.mts +166 -42
- package/types/engine/pack-config.d.mts +13 -0
- package/types/engine/site-build.d.mts +38 -7
- package/types/manifest.d.mts +67 -1
- package/types/sohl/kb-passes.d.mts +5 -1
package/CONTENT.md
CHANGED
|
@@ -77,8 +77,10 @@ paths:
|
|
|
77
77
|
stage: build/stage/packs
|
|
78
78
|
unpack: build/tmp/packs
|
|
79
79
|
|
|
80
|
-
# The one pack list.
|
|
81
|
-
#
|
|
80
|
+
# The one pack list. `packDirectories` and the manifest's `packs` array are both
|
|
81
|
+
# derived from it, so order it for a reader browsing compendiums — the compile
|
|
82
|
+
# order is worked out separately, from what each pass reads (see "Declaration
|
|
83
|
+
# order is presentation" below).
|
|
82
84
|
packs:
|
|
83
85
|
- { name: items, type: Item, label: Items, folders: item-folders.yaml }
|
|
84
86
|
- { name: journals, type: JournalEntry, label: Journals }
|
|
@@ -357,6 +359,55 @@ hand-authored `system.template.json` lived, and the package-id guard and the
|
|
|
357
359
|
top-level `compatibility.minimum`, the id is derived from `package.json`
|
|
358
360
|
`name`, and `@heroiclands/package-build` writes the manifest from this file.
|
|
359
361
|
|
|
362
|
+
### Declaration order is presentation, not compile order
|
|
363
|
+
|
|
364
|
+
`packs:` is the manifest's `packs` array as well, so a consumer orders it for a
|
|
365
|
+
reader browsing compendiums. It is **not** the order the passes run in, and it
|
|
366
|
+
does not have to be: the compile order is derived from what each pass reads.
|
|
367
|
+
|
|
368
|
+
One pass reads another's output today. The actors pass resolves each being's
|
|
369
|
+
embedded items against the JSON the item passes wrote — a being names an item by
|
|
370
|
+
`(type, shortcode)` and never by the pack it ships in, so **every** Item pack has
|
|
371
|
+
to be compiled before the Actor pass, not merely the first. A compiler states
|
|
372
|
+
that on itself:
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
export class Actors extends BasePackCompiler {
|
|
376
|
+
static readsPackOutputOf = Object.freeze(["Item"]);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The generator schedules each pass after the packs of every type it names, and
|
|
381
|
+
does so with the **smallest** reordering that works — the earliest declared pass
|
|
382
|
+
whose dependencies have all run goes next. A list already in a workable order is
|
|
383
|
+
therefore compiled exactly as declared, and one that is not moves only the
|
|
384
|
+
passes that had to move. When the two orders differ the build says so:
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
[INFO]: Pass order: characteristics, mysteries, characters — a pass that reads
|
|
388
|
+
another's output compiles after it, whatever order `packs:` declares.
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
This used to be the author's problem, and a nasty one: an Actor pack declared
|
|
392
|
+
first compiled only where an earlier run had already left `build/packs-json`
|
|
393
|
+
populated. `build/` is gitignored, so it was green on every local tree that had
|
|
394
|
+
built once and exit 1 on every fresh checkout and CI runner, over a message that
|
|
395
|
+
named a missing directory rather than the ordering that caused it (#73). A
|
|
396
|
+
consumer registering a compiler of its own declares its dependencies the same
|
|
397
|
+
way; a type no pack of which is declared is simply not waited for.
|
|
398
|
+
|
|
399
|
+
**Compiling one pack by name is the case ordering cannot answer.**
|
|
400
|
+
`content-build package compile <name>` runs the pass you asked for and no other,
|
|
401
|
+
so a dependency that is neither in the run nor already on disk is reported
|
|
402
|
+
rather than ordered around:
|
|
403
|
+
|
|
404
|
+
```text
|
|
405
|
+
error: pack "characters" (Actor) reads the compiled output of the Item pack
|
|
406
|
+
"characteristics", which this run does not compile and which
|
|
407
|
+
build/packs-json/characteristics does not hold — compile the whole
|
|
408
|
+
package, or compile "characteristics" first
|
|
409
|
+
```
|
|
410
|
+
|
|
360
411
|
### An item type's default art
|
|
361
412
|
|
|
362
413
|
A note that carries no `img:` gets its type's **default art**, and a type
|
|
@@ -423,19 +474,21 @@ npx content-build markdown [paths..] [--fix]
|
|
|
423
474
|
npx content-build manifest [root] [--out <dir>]
|
|
424
475
|
npx content-build site [--out <dir>]
|
|
425
476
|
npx content-build reachability <dir> [file] [--index <shortcode>]
|
|
477
|
+
npx content-build addresses diff --from <zip|dir> [--strict]
|
|
426
478
|
```
|
|
427
479
|
|
|
428
|
-
| Command | What it does
|
|
429
|
-
| -------------- |
|
|
430
|
-
| `package` | Compile the content tree into LevelDB packs, unpack a shipped pack back to JSON, or clean one. See [Install](#install).
|
|
431
|
-
| `docs` | Render a generated reference from the configured registries. `item-fields` is the item-frontmatter page.
|
|
432
|
-
| `lint` | Check a content tree's addresses and its frontmatter. See [Linting a content tree](#linting-a-content-tree).
|
|
433
|
-
| `links` | Check that every link in the tree lands: dead anchors, dead qualified addresses, wikilinks in frontmatter, drifted manifests. |
|
|
434
|
-
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown).
|
|
435
|
-
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to.
|
|
436
|
-
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest).
|
|
437
|
-
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website).
|
|
438
|
-
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point.
|
|
480
|
+
| Command | What it does |
|
|
481
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
482
|
+
| `package` | Compile the content tree into LevelDB packs, unpack a shipped pack back to JSON, or clean one. See [Install](#install). |
|
|
483
|
+
| `docs` | Render a generated reference from the configured registries. `item-fields` is the item-frontmatter page. |
|
|
484
|
+
| `lint` | Check a content tree's addresses and its frontmatter. See [Linting a content tree](#linting-a-content-tree). |
|
|
485
|
+
| `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. |
|
|
486
|
+
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown). |
|
|
487
|
+
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to. |
|
|
488
|
+
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest). |
|
|
489
|
+
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website). |
|
|
490
|
+
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point. |
|
|
491
|
+
| `addresses` | Report every published item address this build has stopped publishing. See [Diffing published addresses](#diffing-published-addresses). |
|
|
439
492
|
|
|
440
493
|
Every path, pack name and root it needs comes from the consuming repository's
|
|
441
494
|
`package-build.config.yaml`, so the usual invocation takes no arguments beyond
|
|
@@ -454,7 +507,7 @@ npx content-build lint # the configured `paths.content`
|
|
|
454
507
|
npx content-build lint some/tree # or a tree named outright
|
|
455
508
|
```
|
|
456
509
|
|
|
457
|
-
Checks the
|
|
510
|
+
Checks the three rules every note's **identity** is authored against, and reports
|
|
458
511
|
each finding in the located form below:
|
|
459
512
|
|
|
460
513
|
- **Shape** — a `shortcode` is strictly ASCII-alphanumeric. It is the identity
|
|
@@ -463,12 +516,63 @@ each finding in the located form below:
|
|
|
463
516
|
- **Uniqueness** — `(type, shortcode)` names one note. A document is addressed
|
|
464
517
|
across _every_ pack of its document type, so routing two same-address notes to
|
|
465
518
|
different packs with `pack:` does not separate them.
|
|
519
|
+
- **The package's own address** — exactly one note claims `/<package>/`. See
|
|
520
|
+
[Exactly one homepage](#exactly-one-homepage) below.
|
|
466
521
|
|
|
467
522
|
It compiles nothing, opens no LevelDB and needs no Foundry manifest, so it runs
|
|
468
523
|
in about a second and can gate a commit. An empty or untyped tree **fails**
|
|
469
524
|
rather than passing: "every one of nothing is unique" is a vacuous pass, and it
|
|
470
525
|
is exactly what a tree that failed to check out produces.
|
|
471
526
|
|
|
527
|
+
What that guard reports is an **empty walk**, not an empty set of addresses. A
|
|
528
|
+
note may be keyless by design — a homepage carries no `shortcode`, because it is
|
|
529
|
+
addressed by the package rather than by a slug — so a package in
|
|
530
|
+
`publish.site: homepage` mode has a content tree that is populated, correct and
|
|
531
|
+
permanently unkeyed. That tree passes; a tree holding no notes at all still
|
|
532
|
+
fails.
|
|
533
|
+
|
|
534
|
+
### Exactly one homepage
|
|
535
|
+
|
|
536
|
+
A content tree declares **exactly one** `type: homepage` note (#52). Zero is an
|
|
537
|
+
error and two is an error, at the same severity, because they are one defect: a
|
|
538
|
+
package whose front page is not the page a person chose.
|
|
539
|
+
|
|
540
|
+
- _Zero_ and the package serves nothing at `/<package>/` — the failure the
|
|
541
|
+
authored homepage exists to prevent, and a silent one: the site build reports
|
|
542
|
+
`wrote 0 homepage(s)` and exits 0.
|
|
543
|
+
- _Two_ and it serves a page nobody chose. Every homepage is written to the same
|
|
544
|
+
`_index.md`, so the second overwrites the first and the front page is decided
|
|
545
|
+
by the order the walk reached the files in — by _filename_, on a type whose
|
|
546
|
+
whole point is that it is routed by frontmatter. There is no "first wins"
|
|
547
|
+
convention to fall back on, so nothing can pick the right one.
|
|
548
|
+
|
|
549
|
+
Neither has a safe default, so neither is a warning: a build that proceeded past
|
|
550
|
+
either would publish the wrong front page while reporting success, which is
|
|
551
|
+
exactly what a warning tolerates.
|
|
552
|
+
|
|
553
|
+
**Where it fires: `lint` _and_ `site`.** No single command reaches every
|
|
554
|
+
package — `HarnMaster-3-FoundryVTT` runs `content-build site` and no
|
|
555
|
+
`content-build lint`; `sohl-thalorna` runs `content-build lint` and its own site
|
|
556
|
+
builder — so a rule in one of them is a rule two of the six packages do not
|
|
557
|
+
have. Both call the same function, so there is one rule and two call sites
|
|
558
|
+
rather than two rules. In the site build it runs **before the output tree is
|
|
559
|
+
cleared**, so a failing gate cannot destroy a good site to report a bad tree.
|
|
560
|
+
|
|
561
|
+
**It does not vary by `publish.site`.** That setting chooses whether the
|
|
562
|
+
_content_ surfaces are published; the homepage is the floor beneath both modes.
|
|
563
|
+
The lint call site reads no `site:` block at all, and so could not vary by mode
|
|
564
|
+
even if the rule wanted to.
|
|
565
|
+
|
|
566
|
+
Zero has no file to name, so the locator is the **content root** — a real path,
|
|
567
|
+
and the directory the note has to be added to. No line or column is invented for
|
|
568
|
+
it. Two is reported once per offending note, located at its own `type:` value and
|
|
569
|
+
naming the other, because each note is a place an author has to open and edit:
|
|
570
|
+
|
|
571
|
+
```text
|
|
572
|
+
assets/content: error: holds no `type: homepage` note, so package "sohl" publishes nothing at its own address /sohl/ — a package's front page is one authored note in this tree, routed by `type:` rather than by filename
|
|
573
|
+
assets/content/homepage.md:3:7: error: duplicate `type: homepage` note, also declared by assets/content/Landing.md; a package has one front page, at /sohl/, and every homepage is written to the same `_index.md` — so the one the walk reaches last silently overwrites the rest
|
|
574
|
+
```
|
|
575
|
+
|
|
472
576
|
### Frontmatter, against the schema its type declares
|
|
473
577
|
|
|
474
578
|
The same command also checks that each note's `sohl:` block is what its **type**
|
|
@@ -512,6 +616,110 @@ does not exist. Removing it was verified output-neutral first: across 1,735
|
|
|
512
616
|
stripped notes, `package compile` produced byte-identical `build/packs-json` and
|
|
513
617
|
the site build byte-identical `site/content`.
|
|
514
618
|
|
|
619
|
+
### The homepage carries no address of its own
|
|
620
|
+
|
|
621
|
+
A note's URL derives from `name.full` and its identity from
|
|
622
|
+
`(type, shortcode)`. The homepage is the one page for which neither holds: it
|
|
623
|
+
publishes at `/<package>/`, fixed by the package id. So `content-build lint`
|
|
624
|
+
**refuses** `name`, `shortcode` and `id` on a `type: homepage` note (#53) rather
|
|
625
|
+
than ignoring them — an author fluent in the conventions writes them here
|
|
626
|
+
expecting exactly what they do everywhere else, and gets none of it.
|
|
627
|
+
|
|
628
|
+
They were never inert, which is why ignoring them was the wrong answer. A
|
|
629
|
+
`shortcode` puts the note in the address index and in the `dataview` link
|
|
630
|
+
universe, so `[[homepage-<shortcode>]]` resolves _green_ — to `homepage/<slug>/`,
|
|
631
|
+
an address derived from `name.full` that the site build never writes, because a
|
|
632
|
+
homepage goes to `_index.md` at the package root. A build reporting a live link
|
|
633
|
+
to a 404 is worse than one saying nothing. It also inflates this command's own
|
|
634
|
+
address tally, so the lint and the link manifest disagree about what the package
|
|
635
|
+
publishes: SoHL's tree reports `1607 address(es) across 1607 note(s)` with a
|
|
636
|
+
`shortcode` on its landing and `1606 address(es) across 1607 note(s)` without
|
|
637
|
+
one. That is one defect, not two — the tally is only ever printed on a clean run,
|
|
638
|
+
so refusing the field is what makes the count honest.
|
|
639
|
+
|
|
640
|
+
Each finding is located at the offending key and says what the field would have
|
|
641
|
+
decided, not merely that it does not belong:
|
|
642
|
+
|
|
643
|
+
```text
|
|
644
|
+
assets/content/homepage.md:26:1: error: `shortcode` decides nothing on a `type: homepage` note: this page's address is the package's own, `/<package>/`, fixed by the package id. It is not ignored either — it puts the note in the address index, so `[[homepage-<shortcode>]]` resolves to a page the site build never writes. Delete it
|
|
645
|
+
assets/content/homepage.md:28:1: error: `name` decides nothing on a `type: homepage` note: a page's slug derives from `name.full`, and a homepage's destination is fixed — it is written to `_index.md` at the package's own address, `/<package>/`. Write `title:` for what the page is called, and delete `name`
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**A named class, not an allow-list.** The documented envelope is `type` plus an
|
|
649
|
+
optional `title`, with `landing`, `description` and `banner` legitimate beside
|
|
650
|
+
them — but an unknown top-level key is **not** refused, and that boundary is the
|
|
651
|
+
decision rather than an omission. A homepage's frontmatter is emitted into the
|
|
652
|
+
published page, so an unrecognised key is a Hugo or theme parameter this build
|
|
653
|
+
has never heard of and has no standing to reject; a closed list would make every
|
|
654
|
+
new theme parameter wait on a package-build release. What is refused is the
|
|
655
|
+
specific class that makes a false claim about _where this page is_. `aliases` is
|
|
656
|
+
not in that class either: it is already dropped from every emitted page, so
|
|
657
|
+
authoring one here is the same no-op it is anywhere else.
|
|
658
|
+
|
|
659
|
+
**Where it fires: `content-build lint` only.** Unlike a rule about the shape of
|
|
660
|
+
the _tree_, which the site build has its own reason to gate on, this is a
|
|
661
|
+
_frontmatter-schema_ rule and `content-build site` runs none of them — wiring in one type's field
|
|
662
|
+
rule would have the site build refuse `shortcode` on a homepage while accepting
|
|
663
|
+
`weight: heavy` on a weapon. The gap that leaves is `HarnMaster-3-FoundryVTT`,
|
|
664
|
+
which runs no `content-build lint` at all and so receives no frontmatter finding
|
|
665
|
+
of any kind; that is a missing script in that repository, not a rule to duplicate
|
|
666
|
+
one at a time.
|
|
667
|
+
|
|
668
|
+
### The homepage's own links
|
|
669
|
+
|
|
670
|
+
The homepage is the page a reader arrives at, and until #54 it was the one page
|
|
671
|
+
nothing checked. SoHL's landing pointed at `kb/creature/` and `kb/character/`
|
|
672
|
+
from the day those two types merged into `being` — two 404s on the package's
|
|
673
|
+
front page, through every build, because a landing's links went through no
|
|
674
|
+
checker at all.
|
|
675
|
+
|
|
676
|
+
`links` therefore audits a `type: homepage` note as well, and it reads **both**
|
|
677
|
+
halves of it. Of the six homepages authored today four carry every link in the
|
|
678
|
+
body as ordinary markdown and two carry them in `landing:` — and the one whose
|
|
679
|
+
dead links prompted this has an _empty body_. A dead link in a card is exactly
|
|
680
|
+
as broken as one in a paragraph, so `landing.install.url`, every
|
|
681
|
+
`cards….url` / `.href`, the markdown links inside the prose fields (`lead`,
|
|
682
|
+
`closing`, `install.intro`, `install.note`, a card's `description`, a link's
|
|
683
|
+
`note`) and the body's own markdown links are all read.
|
|
684
|
+
|
|
685
|
+
**`url` and `href` are not the same address and are not checked the same way.**
|
|
686
|
+
The theme resolves a `url` against the site with `relURL`, so a package writes
|
|
687
|
+
`kb/rules/` and is served `/sohl/kb/rules/` without naming its own prefix; an
|
|
688
|
+
`href` is an address that is _already_ resolved and is used verbatim, which is
|
|
689
|
+
what `cards.source: sections` fills in. A leading `/` is therefore a defect in a
|
|
690
|
+
`url` — Hugo prefixes it a second time — and correct in an `href`.
|
|
691
|
+
|
|
692
|
+
Four findings, and each one names the form to write instead:
|
|
693
|
+
|
|
694
|
+
| Finding | Why |
|
|
695
|
+
| ---------------------------- | --------------------------------------------------------------------------------- |
|
|
696
|
+
| A **retired content type** | `kb/creature/` when `creature` became `being`. The engine knows what was retired. |
|
|
697
|
+
| A **hardcoded absolute URL** | Into this package's own prefix, or into one a vendored manifest names. |
|
|
698
|
+
| A **root-relative `url:`** | `relURL` prefixes it again. `href:` is exempt — verbatim is what it means. |
|
|
699
|
+
| A **wikilink** | Nothing resolves one here: a homepage is published verbatim in every mode. |
|
|
700
|
+
|
|
701
|
+
That last one is why a homepage does **not** get the wikilink resolution every
|
|
702
|
+
other note body gets. In `homepage` mode the content tree is never walked, so
|
|
703
|
+
there is no index for a wikilink to resolve against — and giving the page one
|
|
704
|
+
would make the mode depend on exactly the machinery its licensing fence exists
|
|
705
|
+
to not build. So a landing addresses the web the way the web does, and a
|
|
706
|
+
wikilink on one is reported rather than resolved.
|
|
707
|
+
|
|
708
|
+
**What is checkable, and what is not.** Only an address into this site is, and
|
|
709
|
+
only against facts the build already holds — the retired-type table and the
|
|
710
|
+
package prefixes a vendored manifest names. Two things are deliberately not
|
|
711
|
+
attempted:
|
|
712
|
+
|
|
713
|
+
- **Whether an external URL answers.** There is no network at build time, and a
|
|
714
|
+
build must not go red because a third party is down.
|
|
715
|
+
- **Whether a live in-site address names a page that exists.** Several surfaces
|
|
716
|
+
a landing routes to are produced by other tools entirely — generated API
|
|
717
|
+
documentation, hand-authored Hugo sections — so this build does not hold the
|
|
718
|
+
set of published pages and would report a working link as dead. A bare
|
|
719
|
+
`https://www.heroiclands.org/<package>/` is left alone for the same reason it
|
|
720
|
+
cannot be improved: a package homepage is in no link manifest, so there is no
|
|
721
|
+
better form to write.
|
|
722
|
+
|
|
515
723
|
## Prose: formatting and markdown
|
|
516
724
|
|
|
517
725
|
```bash
|
|
@@ -676,6 +884,10 @@ title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.t
|
|
|
676
884
|
What the module is, which system it needs, how to install it.
|
|
677
885
|
```
|
|
678
886
|
|
|
887
|
+
A package declares **exactly one** of these, and both `content-build lint` and
|
|
888
|
+
`content-build site` require it — see
|
|
889
|
+
[Exactly one homepage](#exactly-one-homepage).
|
|
890
|
+
|
|
679
891
|
That is the whole envelope. A homepage **compiles into no compendium
|
|
680
892
|
document**, appears in no pack and in no link manifest, and is addressed by the
|
|
681
893
|
_package_ rather than by its own name — so `name.full`, `shortcode` and `id`
|
|
@@ -740,7 +952,10 @@ site:
|
|
|
740
952
|
trees:
|
|
741
953
|
- { from: kb/dev-docs, section: dev-docs }
|
|
742
954
|
sections:
|
|
743
|
-
being:
|
|
955
|
+
being:
|
|
956
|
+
title: Beings
|
|
957
|
+
banner: banners/creature.webp
|
|
958
|
+
description: Folk, animals and the things that walk the world.
|
|
744
959
|
readmeSections:
|
|
745
960
|
dev-docs: { title: Developer Documentation, banner: banners/dev-docs.webp }
|
|
746
961
|
```
|
|
@@ -750,7 +965,7 @@ site:
|
|
|
750
965
|
| `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
|
|
751
966
|
| `base` | Where the package is served. Defaults to `/<contentPackage>/`. |
|
|
752
967
|
| `packages` | Which content packages this site renders. Defaults to its own. |
|
|
753
|
-
| `sections` |
|
|
968
|
+
| `sections` | What each section says about itself on its landing — see below. |
|
|
754
969
|
| `readmeSections` | The same, for a section whose landing comes from a `README`. |
|
|
755
970
|
| `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
|
|
756
971
|
| `backfillSections` | Write a bare `_index.md` for any other section directly under the mount. |
|
|
@@ -758,6 +973,42 @@ site:
|
|
|
758
973
|
| `pass` | A named bundle of this repository's own body rewrites. |
|
|
759
974
|
| `passOptions` | That bundle's options. |
|
|
760
975
|
|
|
976
|
+
### What a section may declare
|
|
977
|
+
|
|
978
|
+
A generated section landing is the **only** place a section can describe itself.
|
|
979
|
+
A content package authors no `_index.md` for `weapongear` or `affliction`, so
|
|
980
|
+
the file the theme reads is the one this build writes from `sections` — and
|
|
981
|
+
whatever that entry may carry is the whole of what the section can say.
|
|
982
|
+
|
|
983
|
+
| Key | Required | What it does |
|
|
984
|
+
| ------------- | -------- | ------------------------------------------------------------------------- |
|
|
985
|
+
| `title` | yes | The landing's heading, so it matches the card that links to it. |
|
|
986
|
+
| `banner` | no | The hero image, resolved as a CDN asset like any other `banner:`. |
|
|
987
|
+
| `description` | no | The hero standfirst under the heading, and the blurb a landing card uses. |
|
|
988
|
+
|
|
989
|
+
`readmeSections` takes the same three, for a section whose landing comes from a
|
|
990
|
+
`README` rather than from nothing. What the section declares wins over what the
|
|
991
|
+
`README` happens to carry — the landing has to match the card that links to it.
|
|
992
|
+
|
|
993
|
+
**The vocabulary is closed, and a key outside it is refused by name:**
|
|
994
|
+
|
|
995
|
+
```text
|
|
996
|
+
package-build config: `site.sections.affliction.descrption` is not a
|
|
997
|
+
recognized option (expected one of: title, banner, description).
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
That refusal is the point. `landing` is passed through unvalidated because it is
|
|
1001
|
+
written once, for the mount, in one landing template's own vocabulary. A section
|
|
1002
|
+
entry is written fourteen to twenty times per build against a contract every
|
|
1003
|
+
package and every section shares, so an unbounded one would let a mistyped
|
|
1004
|
+
`descrption:` publish into front matter, be read by nobody, and say nothing to
|
|
1005
|
+
anyone. Refusing it costs one line here when the vocabulary genuinely grows, and
|
|
1006
|
+
buys a build that cannot quietly emit a key no theme reads.
|
|
1007
|
+
|
|
1008
|
+
The **writers** name no keys: a section's `_index.md` is whatever the entry
|
|
1009
|
+
resolved to, `title` first. So extending the vocabulary is a change to the
|
|
1010
|
+
schema alone, and the two can no longer drift apart.
|
|
1011
|
+
|
|
761
1012
|
### Why `out` is required
|
|
762
1013
|
|
|
763
1014
|
The output tree is a build artifact and is **deleted on every run**, so that a
|
|
@@ -778,6 +1029,16 @@ rewrites repository-relative links in the developer docs to their published or
|
|
|
778
1029
|
GitHub addresses. Neither rewrite can fail a build; an unknown `{@link}` degrades
|
|
779
1030
|
to a code span.
|
|
780
1031
|
|
|
1032
|
+
`symbolMap` is resolved **against the repository root**, not the process cwd, so
|
|
1033
|
+
`content-build site` reads the same map whatever directory it was invoked from.
|
|
1034
|
+
Leaving it unset is the legitimate empty case — every `{@link}` degrades, and
|
|
1035
|
+
nothing is reported. Setting it to a path that cannot be read, cannot be parsed,
|
|
1036
|
+
or does not hold a name → page object **fails the build**, naming the file and
|
|
1037
|
+
the reason: those were all indistinguishable from "no symbols" until #75, so a
|
|
1038
|
+
site could publish 224 dead `{@link}` tags at exit 0. A map that is read reports
|
|
1039
|
+
its symbol count at info level, which is the only way to tell a map that loaded
|
|
1040
|
+
from one that loaded empty without reading the emitted HTML.
|
|
1041
|
+
|
|
781
1042
|
A bundle supplies up to two hooks, and their order around the shared work is the
|
|
782
1043
|
point:
|
|
783
1044
|
|
|
@@ -808,6 +1069,72 @@ None of them exits the process from inside the library; the command decides. Tha
|
|
|
808
1069
|
is what makes them testable, which the consumer scripts' inline `process.exit`
|
|
809
1070
|
calls were not.
|
|
810
1071
|
|
|
1072
|
+
## Diffing published addresses
|
|
1073
|
+
|
|
1074
|
+
```bash
|
|
1075
|
+
gh release download v0.8.2 -p system.zip -D build/baseline
|
|
1076
|
+
npx content-build addresses diff --from build/baseline/system.zip
|
|
1077
|
+
npx content-build addresses diff --from build/baseline/system.zip --strict
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
A package's `(type, shortcode)` addresses are a **published interface**. Every
|
|
1081
|
+
satellite declaring `itemCatalog: true` assembles its beings out of them —
|
|
1082
|
+
`attribute:str`, `skill:awar`, `weapongear:Tabri` — resolving each against the
|
|
1083
|
+
Item packs of the release its `compatibility.verified` pins. Renaming a
|
|
1084
|
+
shortcode is therefore a breaking change to something other repositories
|
|
1085
|
+
consume, and it used to cost nothing and produce no signal: the check that got
|
|
1086
|
+
made was a repository-local grep, which cannot see the other repositories and
|
|
1087
|
+
reports the reassuring answer.
|
|
1088
|
+
|
|
1089
|
+
`sohl` renamed one weapon's shortcode from `Tabri` to `Taburi` two days after
|
|
1090
|
+
the `v0.8.2` tag, on the stated ground that "nothing referenced the old value".
|
|
1091
|
+
True of that repository. Both satellites pin `v0.8.2` and address
|
|
1092
|
+
`weapongear:Tabri` on their copy of the same character — five lookups that
|
|
1093
|
+
resolve today and fail the moment either pin moves, reporting a missing item.
|
|
1094
|
+
|
|
1095
|
+
**A rename is told from a removal by the document id, and that is an identity
|
|
1096
|
+
match rather than an inference.** A note authors its `_id` in frontmatter; it is
|
|
1097
|
+
not derived from the shortcode, so it survives a rename. An address that
|
|
1098
|
+
disappeared while its document is still published elsewhere _is_ a rename:
|
|
1099
|
+
|
|
1100
|
+
```text
|
|
1101
|
+
assets/content/Weapons/Melee/Taburi.md:12:1: warning: since sohl@0.8.2, weapongear:Tabri is no longer published; the same document (s5D6QJbw7ZbETxdN) is now published as weapongear:Taburi. Every package that resolves weapongear:Tabri breaks when it moves past sohl@0.8.2
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
Where the id is published under no address at all, that is all it says —
|
|
1105
|
+
**withdrawn**, with no successor named. A split, a deletion and a merge are
|
|
1106
|
+
indistinguishable at that point, and a "did you mean" guessed from string
|
|
1107
|
+
similarity would be worse than silence, because a wrong one sends the reader to
|
|
1108
|
+
the wrong fix.
|
|
1109
|
+
|
|
1110
|
+
| Finding | What it means | Severity |
|
|
1111
|
+
| ----------- | -------------------------------------------------------- | --------------------------------------- |
|
|
1112
|
+
| `renamed` | Address gone; the same document publishes under another. | `warning` — legitimate, but not silent |
|
|
1113
|
+
| `withdrawn` | Address gone; its document publishes under none. | `warning` — retiring content is allowed |
|
|
1114
|
+
|
|
1115
|
+
Neither fails a build. Retiring content is legitimate, and so is renaming — the
|
|
1116
|
+
shortcode charset rule forces some. What a rename must not do is happen without
|
|
1117
|
+
anyone noticing. `--strict` reports both as errors and exits non-zero, for a
|
|
1118
|
+
release workflow that wants a gate.
|
|
1119
|
+
|
|
1120
|
+
**A finding is placed against the note, not the pack it was read from.** The
|
|
1121
|
+
address space is read from compiled output because that is what actually ships;
|
|
1122
|
+
the content tree is read only to find the note carrying the id, so the reader
|
|
1123
|
+
lands on the `shortcode:` line they just edited. A withdrawal has no such note,
|
|
1124
|
+
so it degrades to the baseline document — and where neither is readable the
|
|
1125
|
+
position is dropped rather than guessed.
|
|
1126
|
+
|
|
1127
|
+
**The baseline is named, never derived, and never downloaded.** `--from` takes
|
|
1128
|
+
the artifact for the same reason `deps fetch --from` does: a command that
|
|
1129
|
+
reaches the network on its own is not reproducible and fails strangely offline.
|
|
1130
|
+
A baseline that yields **no** addressable item is refused rather than read as
|
|
1131
|
+
"nothing changed" — it would report a clean result for every possible input,
|
|
1132
|
+
which is the one failure a check like this can never catch.
|
|
1133
|
+
|
|
1134
|
+
Item packs only, because that is the address space consumers resolve against: a
|
|
1135
|
+
being's embedded items are the only cross-package resolution by
|
|
1136
|
+
`(type, shortcode)`.
|
|
1137
|
+
|
|
811
1138
|
## Diagnostics
|
|
812
1139
|
|
|
813
1140
|
Every warning or error a build reports **about a content note** is emitted in the
|
package/MIGRATING.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
# Migrating to `@heroiclands/package-build` 6.0.0
|
|
2
|
+
|
|
3
|
+
**No configuration change to make, and one build check that may now fail.**
|
|
4
|
+
`packageBuild.manifest.packFolders` is compared against the `packs[]` the build
|
|
5
|
+
derives, and a folder naming a pack the package does not ship is an error.
|
|
6
|
+
|
|
7
|
+
## 1. Check what `package-build manifest` says
|
|
8
|
+
|
|
9
|
+
Nothing to edit up front — run it, and the build names anything wrong:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
package-build.config.yaml:164:23: error: packFolders: folder "HârnMaster 3 System" names pack "character", which this package does not ship (packs: items, system-help)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Every name in a folder's `packs` must appear in the top-level `packs:` list —
|
|
16
|
+
companions included, since Foundry sees no difference. Delete a name that no
|
|
17
|
+
longer resolves, or correct it. An error stops the manifest being written, so
|
|
18
|
+
nothing half-right reaches the stage.
|
|
19
|
+
|
|
20
|
+
## 2. The advisory needs no action
|
|
21
|
+
|
|
22
|
+
A pack no folder names is a **warning**, and the build continues:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Shipping one pack at the root can be deliberate, so this is not an error. But a
|
|
29
|
+
package that bothered to declare a folder rarely meant to leave one out — that
|
|
30
|
+
is exactly how `HarnMaster-3-FoundryVTT` shipped 1,577 of 1,597 documents loose
|
|
31
|
+
beside its folder. Add the pack to a folder, or leave it and take the advisory.
|
|
32
|
+
|
|
33
|
+
A package that declares no `packFolders` at all is unaffected, and says nothing.
|
|
34
|
+
|
|
35
|
+
## 3. Nothing else
|
|
36
|
+
|
|
37
|
+
- **No configuration key changed**, and no CLI command, flag or exit code beyond
|
|
38
|
+
`manifest` failing on the error above.
|
|
39
|
+
- `writeManifest` takes an optional `configFile`, so a finding names the line it
|
|
40
|
+
is about. Omitting it costs the position, not the finding.
|
|
41
|
+
|
|
1
42
|
# Migrating to `@heroiclands/package-build` 5.0.0
|
|
2
43
|
|
|
3
44
|
**One configuration change: `publish.site` is a mode, not a boolean.** And one
|
package/README.md
CHANGED
|
@@ -255,6 +255,36 @@ comes from.
|
|
|
255
255
|
a companion is only a pack written by another pass rather than one of its own.
|
|
256
256
|
Give each pack the `label` you want Foundry to show.
|
|
257
257
|
|
|
258
|
+
#### `packFolders` is checked against the packs you ship
|
|
259
|
+
|
|
260
|
+
`packFolders` is the one **declared** key that names something the build
|
|
261
|
+
**derives**. Every other declared key states a fact about the package (`title`,
|
|
262
|
+
`socket`, `grid`) or addresses a staged file (`esmodules`, `styles`,
|
|
263
|
+
`languages`) — a staged file being a different relation, answered against the
|
|
264
|
+
stage rather than against configuration. So it is the one place a declaration
|
|
265
|
+
can quietly go stale against a value the build already computed, and it did:
|
|
266
|
+
`HarnMaster-3-FoundryVTT` shipped a folder naming four packs, three of which had
|
|
267
|
+
not existed since its compendium was consolidated, while `items` — 1,577 of
|
|
268
|
+
1,597 documents — sat in no folder at all, with the build reporting nothing.
|
|
269
|
+
|
|
270
|
+
`package-build manifest` now compares the two, descending through nested folders
|
|
271
|
+
(Foundry allows three levels), and reports in the usual
|
|
272
|
+
`file:line:column: severity: message` form:
|
|
273
|
+
|
|
274
|
+
| Finding | Severity | Why |
|
|
275
|
+
| ----------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
276
|
+
| a folder names a pack the package does not ship | **error** | Foundry silently skips a name it cannot resolve, so the declaration does nothing; no arrangement intends it |
|
|
277
|
+
| a pack no folder names | **warning** | legal, and a root-level pack can be deliberate — but a package that declared a folder rarely meant to leave one out |
|
|
278
|
+
| no `packFolders` declared at all | nothing | everything at the root is an arrangement, not an omission |
|
|
279
|
+
|
|
280
|
+
An error **stops the write**: a manifest already known to describe packs that do
|
|
281
|
+
not exist should not reach the stage, where the next command would deploy it.
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
package-build.config.yaml:164:23: error: packFolders: folder "HârnMaster 3 System" names pack "character", which this package does not ship (packs: items, system-help)
|
|
285
|
+
package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
|
|
286
|
+
```
|
|
287
|
+
|
|
258
288
|
`compatibility` and `relationships` are read from the **top level** of the
|
|
259
289
|
shared configuration, not from this section — content-build consumes them
|
|
260
290
|
(`supportedCoreVersion`, and a module'''s `stats.systemVersion`) and the
|