@heroiclands/package-build 4.0.0 → 6.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 +401 -0
- package/CONTENT.md +207 -23
- package/MIGRATING.md +114 -0
- package/README.md +30 -0
- package/bin/content-build.mjs +26 -5
- package/bin/package-build.mjs +3 -1
- package/content-config.mjs +109 -6
- package/engine/base-compiler.mjs +28 -3
- package/engine/content-links.mjs +221 -2
- package/engine/content-lint.mjs +12 -3
- package/engine/diagnostics.mjs +46 -0
- package/engine/generate.mjs +156 -7
- package/engine/homepage.mjs +259 -0
- package/engine/index.mjs +6 -0
- package/engine/manifest-emit.mjs +2 -1
- package/engine/note-schemas.mjs +44 -0
- package/engine/pack-config.mjs +21 -0
- package/engine/site-build.mjs +140 -4
- 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/sohl/note-schemas.mjs +8 -0
- package/types/content-config.d.mts +105 -4
- package/types/engine/base-compiler.d.mts +22 -0
- package/types/engine/content-links.d.mts +53 -2
- package/types/engine/diagnostics.d.mts +28 -0
- package/types/engine/generate.d.mts +50 -2
- package/types/engine/homepage.d.mts +125 -0
- package/types/engine/index.d.mts +2 -0
- package/types/engine/note-schemas.d.mts +6 -0
- package/types/engine/pack-config.d.mts +13 -0
- package/types/engine/site-build.d.mts +54 -0
- package/types/manifest.d.mts +67 -1
- package/types/sohl/kb-passes.d.mts +5 -1
- package/types/sohl/note-schemas.d.mts +6 -0
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 }
|
|
@@ -128,9 +130,11 @@ packageBuild:
|
|
|
128
130
|
- { from: assets/icons, to: assets/icons }
|
|
129
131
|
|
|
130
132
|
# Three independent switches — every combination is real — plus the address
|
|
131
|
-
# scheme both `manifest` and `site` derive addresses under.
|
|
133
|
+
# scheme both `manifest` and `site` derive addresses under. `site` is a mode,
|
|
134
|
+
# not a boolean: `homepage` (the default) publishes the authored homepage and
|
|
135
|
+
# no other page; `content` publishes it plus every page the tree compiles to.
|
|
132
136
|
publish:
|
|
133
|
-
site:
|
|
137
|
+
site: content
|
|
134
138
|
manifests: { publish: true, consume: true }
|
|
135
139
|
address:
|
|
136
140
|
prefix: kb/
|
|
@@ -147,8 +151,8 @@ site:
|
|
|
147
151
|
|
|
148
152
|
The loader validates the document, resolves every path against the directory
|
|
149
153
|
the file sits in, fills the optional halves with their defaults
|
|
150
|
-
(`skipDirectories: []`, `packageBuild: {}`, the conventional `paths`,
|
|
151
|
-
|
|
154
|
+
(`skipDirectories: []`, `packageBuild: {}`, the conventional `paths`, both
|
|
155
|
+
manifest switches off and `publish.site` at its `homepage` floor),
|
|
152
156
|
derives `assetRoot`, `packDirectories`, `itemTypes` and `docEntryTypes`, and
|
|
153
157
|
freezes the result. A malformed configuration throws a `TypeError` naming the
|
|
154
158
|
offending field, so it fails at load rather than as an empty pack much later.
|
|
@@ -355,6 +359,55 @@ hand-authored `system.template.json` lived, and the package-id guard and the
|
|
|
355
359
|
top-level `compatibility.minimum`, the id is derived from `package.json`
|
|
356
360
|
`name`, and `@heroiclands/package-build` writes the manifest from this file.
|
|
357
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
|
+
|
|
358
411
|
### An item type's default art
|
|
359
412
|
|
|
360
413
|
A note that carries no `img:` gets its type's **default art**, and a type
|
|
@@ -423,17 +476,17 @@ npx content-build site [--out <dir>]
|
|
|
423
476
|
npx content-build reachability <dir> [file] [--index <shortcode>]
|
|
424
477
|
```
|
|
425
478
|
|
|
426
|
-
| Command | What it does
|
|
427
|
-
| -------------- |
|
|
428
|
-
| `package` | Compile the content tree into LevelDB packs, unpack a shipped pack back to JSON, or clean one. See [Install](#install).
|
|
429
|
-
| `docs` | Render a generated reference from the configured registries. `item-fields` is the item-frontmatter page.
|
|
430
|
-
| `lint` | Check a content tree's addresses and its frontmatter. See [Linting a content tree](#linting-a-content-tree).
|
|
431
|
-
| `links` | Check that every link in the tree lands: dead anchors, dead qualified addresses, wikilinks in frontmatter, drifted manifests. |
|
|
432
|
-
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown).
|
|
433
|
-
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to.
|
|
434
|
-
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest).
|
|
435
|
-
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website).
|
|
436
|
-
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point.
|
|
479
|
+
| Command | What it does |
|
|
480
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
481
|
+
| `package` | Compile the content tree into LevelDB packs, unpack a shipped pack back to JSON, or clean one. See [Install](#install). |
|
|
482
|
+
| `docs` | Render a generated reference from the configured registries. `item-fields` is the item-frontmatter page. |
|
|
483
|
+
| `lint` | Check a content tree's addresses and its frontmatter. See [Linting a content tree](#linting-a-content-tree). |
|
|
484
|
+
| `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. |
|
|
485
|
+
| `format` | Prettier, with the shared configuration. See [Prose: formatting and markdown](#prose-formatting-and-markdown). |
|
|
486
|
+
| `markdown` | markdownlint, with the shared rule set — the structure Prettier is indifferent to. |
|
|
487
|
+
| `manifest` | Emit this package's cross-package link manifest. See [Publishing a link manifest](#publishing-a-link-manifest). |
|
|
488
|
+
| `site` | Publish the content tree as a website. See [Publishing a website](#publishing-a-website). |
|
|
489
|
+
| `reachability` | Walk outward from an index note and report what no path reaches, for a tree meant to be navigable from one entry point. |
|
|
437
490
|
|
|
438
491
|
Every path, pack name and root it needs comes from the consuming repository's
|
|
439
492
|
`package-build.config.yaml`, so the usual invocation takes no arguments beyond
|
|
@@ -467,6 +520,13 @@ in about a second and can gate a commit. An empty or untyped tree **fails**
|
|
|
467
520
|
rather than passing: "every one of nothing is unique" is a vacuous pass, and it
|
|
468
521
|
is exactly what a tree that failed to check out produces.
|
|
469
522
|
|
|
523
|
+
What that guard reports is an **empty walk**, not an empty set of addresses. A
|
|
524
|
+
note may be keyless by design — a homepage carries no `shortcode`, because it is
|
|
525
|
+
addressed by the package rather than by a slug — so a package in
|
|
526
|
+
`publish.site: homepage` mode has a content tree that is populated, correct and
|
|
527
|
+
permanently unkeyed. That tree passes; a tree holding no notes at all still
|
|
528
|
+
fails.
|
|
529
|
+
|
|
470
530
|
### Frontmatter, against the schema its type declares
|
|
471
531
|
|
|
472
532
|
The same command also checks that each note's `sohl:` block is what its **type**
|
|
@@ -510,6 +570,61 @@ does not exist. Removing it was verified output-neutral first: across 1,735
|
|
|
510
570
|
stripped notes, `package compile` produced byte-identical `build/packs-json` and
|
|
511
571
|
the site build byte-identical `site/content`.
|
|
512
572
|
|
|
573
|
+
### The homepage's own links
|
|
574
|
+
|
|
575
|
+
The homepage is the page a reader arrives at, and until #54 it was the one page
|
|
576
|
+
nothing checked. SoHL's landing pointed at `kb/creature/` and `kb/character/`
|
|
577
|
+
from the day those two types merged into `being` — two 404s on the package's
|
|
578
|
+
front page, through every build, because a landing's links went through no
|
|
579
|
+
checker at all.
|
|
580
|
+
|
|
581
|
+
`links` therefore audits a `type: homepage` note as well, and it reads **both**
|
|
582
|
+
halves of it. Of the six homepages authored today four carry every link in the
|
|
583
|
+
body as ordinary markdown and two carry them in `landing:` — and the one whose
|
|
584
|
+
dead links prompted this has an _empty body_. A dead link in a card is exactly
|
|
585
|
+
as broken as one in a paragraph, so `landing.install.url`, every
|
|
586
|
+
`cards….url` / `.href`, the markdown links inside the prose fields (`lead`,
|
|
587
|
+
`closing`, `install.intro`, `install.note`, a card's `description`, a link's
|
|
588
|
+
`note`) and the body's own markdown links are all read.
|
|
589
|
+
|
|
590
|
+
**`url` and `href` are not the same address and are not checked the same way.**
|
|
591
|
+
The theme resolves a `url` against the site with `relURL`, so a package writes
|
|
592
|
+
`kb/rules/` and is served `/sohl/kb/rules/` without naming its own prefix; an
|
|
593
|
+
`href` is an address that is _already_ resolved and is used verbatim, which is
|
|
594
|
+
what `cards.source: sections` fills in. A leading `/` is therefore a defect in a
|
|
595
|
+
`url` — Hugo prefixes it a second time — and correct in an `href`.
|
|
596
|
+
|
|
597
|
+
Four findings, and each one names the form to write instead:
|
|
598
|
+
|
|
599
|
+
| Finding | Why |
|
|
600
|
+
| ---------------------------- | --------------------------------------------------------------------------------- |
|
|
601
|
+
| A **retired content type** | `kb/creature/` when `creature` became `being`. The engine knows what was retired. |
|
|
602
|
+
| A **hardcoded absolute URL** | Into this package's own prefix, or into one a vendored manifest names. |
|
|
603
|
+
| A **root-relative `url:`** | `relURL` prefixes it again. `href:` is exempt — verbatim is what it means. |
|
|
604
|
+
| A **wikilink** | Nothing resolves one here: a homepage is published verbatim in every mode. |
|
|
605
|
+
|
|
606
|
+
That last one is why a homepage does **not** get the wikilink resolution every
|
|
607
|
+
other note body gets. In `homepage` mode the content tree is never walked, so
|
|
608
|
+
there is no index for a wikilink to resolve against — and giving the page one
|
|
609
|
+
would make the mode depend on exactly the machinery its licensing fence exists
|
|
610
|
+
to not build. So a landing addresses the web the way the web does, and a
|
|
611
|
+
wikilink on one is reported rather than resolved.
|
|
612
|
+
|
|
613
|
+
**What is checkable, and what is not.** Only an address into this site is, and
|
|
614
|
+
only against facts the build already holds — the retired-type table and the
|
|
615
|
+
package prefixes a vendored manifest names. Two things are deliberately not
|
|
616
|
+
attempted:
|
|
617
|
+
|
|
618
|
+
- **Whether an external URL answers.** There is no network at build time, and a
|
|
619
|
+
build must not go red because a third party is down.
|
|
620
|
+
- **Whether a live in-site address names a page that exists.** Several surfaces
|
|
621
|
+
a landing routes to are produced by other tools entirely — generated API
|
|
622
|
+
documentation, hand-authored Hugo sections — so this build does not hold the
|
|
623
|
+
set of published pages and would report a working link as dead. A bare
|
|
624
|
+
`https://www.heroiclands.org/<package>/` is left alone for the same reason it
|
|
625
|
+
cannot be improved: a package homepage is in no link manifest, so there is no
|
|
626
|
+
better form to write.
|
|
627
|
+
|
|
513
628
|
## Prose: formatting and markdown
|
|
514
629
|
|
|
515
630
|
```bash
|
|
@@ -594,10 +709,11 @@ It reads its whole input from configuration and takes nothing else:
|
|
|
594
709
|
| `publish.address` | The address scheme those paths are derived under — see below. |
|
|
595
710
|
|
|
596
711
|
**Both addresses are optional, independently.** A note that compiles into no
|
|
597
|
-
document has no `uuid`, and a package that ships compendiums and publishes
|
|
598
|
-
|
|
599
|
-
neither is guessed: inventing the missing one
|
|
600
|
-
exist, which is the silent dead link the manifest
|
|
712
|
+
document has no `uuid`, and a package that ships compendiums and publishes only
|
|
713
|
+
a homepage (`publish.site: homepage`) has no `path` on any entry — its notes are
|
|
714
|
+
not pages. Neither is an error, and neither is guessed: inventing the missing one
|
|
715
|
+
asserts a target that does not exist, which is the silent dead link the manifest
|
|
716
|
+
exists to prevent.
|
|
601
717
|
|
|
602
718
|
**`publish.manifests.publish` is a declaration, not a preference.** The file is
|
|
603
719
|
vendored by other repositories and read as authoritative, so emitting one is a
|
|
@@ -615,7 +731,7 @@ time and 404s for the reader.
|
|
|
615
731
|
|
|
616
732
|
```yaml
|
|
617
733
|
publish:
|
|
618
|
-
site:
|
|
734
|
+
site: content
|
|
619
735
|
manifests: { publish: true, consume: true }
|
|
620
736
|
address:
|
|
621
737
|
prefix: kb/ # default: "" — the package root
|
|
@@ -658,6 +774,64 @@ address derivation, the address index, table expansion, wikilink resolution,
|
|
|
658
774
|
code-fence protection, the foreign-manifest merge, the page emission and the
|
|
659
775
|
section-landing backfill.
|
|
660
776
|
|
|
777
|
+
### The homepage, and how much else is published
|
|
778
|
+
|
|
779
|
+
Every package is reachable at `https://www.heroiclands.org/<contentPackage>/`,
|
|
780
|
+
and what a reader finds there is a note in the content tree — one markdown file,
|
|
781
|
+
written by a person:
|
|
782
|
+
|
|
783
|
+
```markdown
|
|
784
|
+
---
|
|
785
|
+
type: homepage
|
|
786
|
+
title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.title
|
|
787
|
+
---
|
|
788
|
+
|
|
789
|
+
What the module is, which system it needs, how to install it.
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
That is the whole envelope. A homepage **compiles into no compendium
|
|
793
|
+
document**, appears in no pack and in no link manifest, and is addressed by the
|
|
794
|
+
_package_ rather than by its own name — so `name.full`, `shortcode` and `id`
|
|
795
|
+
decide nothing on it. It is dispatched on `type` like every other note, not on a
|
|
796
|
+
filename: `README.md` is already a section landing under `landing: readme`, and
|
|
797
|
+
in `sohl-thalorna` it is a developer explainer about the source tree.
|
|
798
|
+
|
|
799
|
+
`type: homepage` is declared by the **engine**, not by the `sohl` item registry,
|
|
800
|
+
so a package that configures no `itemBuilders` at all — `HarnMaster-3-FoundryVTT`
|
|
801
|
+
and every HM3 module — can author one. The `engine/` ÷ `sohl/` line is
|
|
802
|
+
note-format knowledge against game-system knowledge, and a homepage carries no
|
|
803
|
+
`system` block.
|
|
804
|
+
|
|
805
|
+
`publish.site` then says how much _else_ is published:
|
|
806
|
+
|
|
807
|
+
| Mode | What is published |
|
|
808
|
+
| ---------- | ------------------------------------------------------------------------------- |
|
|
809
|
+
| `homepage` | The authored homepage, and no other page. **The default, and the floor.** |
|
|
810
|
+
| `content` | The homepage plus every page the content tree compiles to, and its extra trees. |
|
|
811
|
+
|
|
812
|
+
There is no value meaning "no web presence": every package publishes its
|
|
813
|
+
homepage. It was a boolean until 5.0.0, and both spellings are now refused
|
|
814
|
+
naming the mode to write instead — see [MIGRATING.md](MIGRATING.md).
|
|
815
|
+
|
|
816
|
+
**Homepage-only is a first-class mode, not an accommodation.**
|
|
817
|
+
`sohl-kethira-basic` (unofficial Hârn fan material under Keléstia Productions'
|
|
818
|
+
Fan Material Guidelines) and `harn-adventures` (HârnFanon under Lythia's terms)
|
|
819
|
+
must each publish a homepage and nothing beneath it. The boundary is _published
|
|
820
|
+
content_ — journal text, artwork, item descriptions, compiled notes — and a page
|
|
821
|
+
announcing the module discloses none of it. Because the failure mode is silent,
|
|
822
|
+
the mode **fences the content surfaces off** rather than trusting a
|
|
823
|
+
configuration to stay empty: in `homepage` mode the tree is never walked for
|
|
824
|
+
pages, and `sections`, `trees`, `landing` and `backfillSections` emit nothing
|
|
825
|
+
even when they are declared.
|
|
826
|
+
|
|
827
|
+
That is separate from `publish.manifests.publish`, which stays off for both for
|
|
828
|
+
an unrelated reason: a link manifest is the dependency edge that would stop the
|
|
829
|
+
module being withdrawable, and a homepage is one row in a routing table.
|
|
830
|
+
|
|
831
|
+
The homepage is written at the root of `site.out` — the package's own address —
|
|
832
|
+
one level above the content mount, which is where `publish.address.prefix` puts
|
|
833
|
+
everything else.
|
|
834
|
+
|
|
661
835
|
**What it does not do is decide addresses.** Those come from `publish.address`,
|
|
662
836
|
the same setting the link manifest reads, so a page and its manifest entry cannot
|
|
663
837
|
disagree about where the page is. Everything under `site:` is _framing_ —
|
|
@@ -686,7 +860,7 @@ site:
|
|
|
686
860
|
|
|
687
861
|
| Key | What it decides |
|
|
688
862
|
| ------------------ | ------------------------------------------------------------------------------------------------ |
|
|
689
|
-
| `out` | The Hugo content root. **Required
|
|
863
|
+
| `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
|
|
690
864
|
| `base` | Where the package is served. Defaults to `/<contentPackage>/`. |
|
|
691
865
|
| `packages` | Which content packages this site renders. Defaults to its own. |
|
|
692
866
|
| `sections` | Landing title and hero per section, so a landing matches the card that links to it. |
|
|
@@ -717,6 +891,16 @@ rewrites repository-relative links in the developer docs to their published or
|
|
|
717
891
|
GitHub addresses. Neither rewrite can fail a build; an unknown `{@link}` degrades
|
|
718
892
|
to a code span.
|
|
719
893
|
|
|
894
|
+
`symbolMap` is resolved **against the repository root**, not the process cwd, so
|
|
895
|
+
`content-build site` reads the same map whatever directory it was invoked from.
|
|
896
|
+
Leaving it unset is the legitimate empty case — every `{@link}` degrades, and
|
|
897
|
+
nothing is reported. Setting it to a path that cannot be read, cannot be parsed,
|
|
898
|
+
or does not hold a name → page object **fails the build**, naming the file and
|
|
899
|
+
the reason: those were all indistinguishable from "no symbols" until #75, so a
|
|
900
|
+
site could publish 224 dead `{@link}` tags at exit 0. A map that is read reports
|
|
901
|
+
its symbol count at info level, which is the only way to tell a map that loaded
|
|
902
|
+
from one that loaded empty without reading the emitted HTML.
|
|
903
|
+
|
|
720
904
|
A bundle supplies up to two hooks, and their order around the shared work is the
|
|
721
905
|
point:
|
|
722
906
|
|
package/MIGRATING.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
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
|
+
|
|
42
|
+
# Migrating to `@heroiclands/package-build` 5.0.0
|
|
43
|
+
|
|
44
|
+
**One configuration change: `publish.site` is a mode, not a boolean.** And one
|
|
45
|
+
new authoring capability that needs no migration: a `type: homepage` note.
|
|
46
|
+
|
|
47
|
+
## 1. Respell `publish.site`
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
publish:
|
|
51
|
+
site: content # was `site: true`
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
publish:
|
|
56
|
+
site: homepage # was `site: false`, or absent
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`homepage` is the default, so a repository that never set the key needs no edit.
|
|
60
|
+
A repository that set it to either boolean gets a `TypeError` at load naming the
|
|
61
|
+
mode to write:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
package-build config: `publish.site` is no longer a boolean — write `site: content`. Every package publishes an authored homepage at /<contentPackage>/, so no value means "no web presence": `homepage` publishes that page and nothing else, and `content` publishes it plus every page the content tree compiles to.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Both spellings are **refused rather than mapped** onto the nearest mode. `false`
|
|
68
|
+
read as _this package has no web presence_, which now describes no package at
|
|
69
|
+
all, and a value silently reinterpreted reads to its author as though it still
|
|
70
|
+
means what it said.
|
|
71
|
+
|
|
72
|
+
Nothing else about publishing moved: `publish.address`, `publish.manifests` and
|
|
73
|
+
the whole `site:` block are unchanged, and every address `sohl` and `thalorna`
|
|
74
|
+
already publish is byte-identical across the upgrade.
|
|
75
|
+
|
|
76
|
+
## 2. Author a homepage (optional here, required by #52)
|
|
77
|
+
|
|
78
|
+
Every package is reachable at `https://www.heroiclands.org/<contentPackage>/`,
|
|
79
|
+
and the page there is a note in the content tree:
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
---
|
|
83
|
+
type: homepage
|
|
84
|
+
title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.title
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
What the module is, which system it needs, how to install it.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
It compiles into no compendium document, appears in no pack and in no link
|
|
91
|
+
manifest, and is addressed by the package rather than by its own name. It is
|
|
92
|
+
written to the root of `site.out`, one level above the content mount.
|
|
93
|
+
|
|
94
|
+
A repository with no homepage note publishes none, and the site build says so in
|
|
95
|
+
its count. Requiring exactly one is a separate change (#52).
|
|
96
|
+
|
|
97
|
+
## 3. What homepage-only means
|
|
98
|
+
|
|
99
|
+
`homepage` mode does not merely leave the content configuration unused — it
|
|
100
|
+
**fences the content surfaces off**. The tree is never walked for pages, and
|
|
101
|
+
`site.sections`, `site.trees`, `site.landing` and `site.backfillSections` emit
|
|
102
|
+
nothing even when they are declared.
|
|
103
|
+
|
|
104
|
+
That is deliberate, and it is a licensing requirement rather than a preference.
|
|
105
|
+
`sohl-kethira-basic` (Keléstia Productions' Fan Material Guidelines) and
|
|
106
|
+
`harn-adventures` (HârnFanon under Lythia's terms) publish a homepage and no
|
|
107
|
+
other page; the failure mode is silent — a `site:` block added later ships
|
|
108
|
+
licensed content with nobody noticing — so the property is asserted by the code
|
|
109
|
+
path rather than left to configuration.
|
|
110
|
+
|
|
111
|
+
`publish.manifests.publish` is a separate decision and stays `false` for both: a
|
|
112
|
+
link manifest is the dependency edge that would stop a module being withdrawable,
|
|
113
|
+
and a homepage is not.
|
|
114
|
+
|
|
1
115
|
# Migrating to `@heroiclands/package-build` 4.0.0
|
|
2
116
|
|
|
3
117
|
**One authoring change: delete `package:` from every content note.** A note's
|
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
|
package/bin/content-build.mjs
CHANGED
|
@@ -76,6 +76,9 @@ import { lintFrontmatter } from "../engine/frontmatter-lint.mjs";
|
|
|
76
76
|
// set — an adventure module ships skills, beings and magic swords — so no
|
|
77
77
|
// consumer gets a subset (#19, #20).
|
|
78
78
|
import { NOTE_SCHEMAS } from "../sohl/note-schemas.mjs";
|
|
79
|
+
// The engine's own types, merged under the registry's so the vocabulary stands
|
|
80
|
+
// in a package that configures no `itemBuilders` at all (#51).
|
|
81
|
+
import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
79
82
|
import { checkFormatting, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
80
83
|
import { emitLinkManifest } from "../engine/manifest-emit.mjs";
|
|
81
84
|
import {
|
|
@@ -346,7 +349,7 @@ function lintCommand() {
|
|
|
346
349
|
skipDirectories: config.skipDirectories,
|
|
347
350
|
});
|
|
348
351
|
const frontmatter = lintFrontmatter(index, {
|
|
349
|
-
schemas: NOTE_SCHEMAS,
|
|
352
|
+
schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
|
|
350
353
|
references: argv.references,
|
|
351
354
|
});
|
|
352
355
|
|
|
@@ -363,7 +366,8 @@ function lintCommand() {
|
|
|
363
366
|
} else {
|
|
364
367
|
log.info(
|
|
365
368
|
`Addresses and frontmatter are well-formed ` +
|
|
366
|
-
`(${addresses.keys} across
|
|
369
|
+
`(${addresses.keys} address(es) across ` +
|
|
370
|
+
`${addresses.notes} note(s)).`,
|
|
367
371
|
);
|
|
368
372
|
}
|
|
369
373
|
} catch (err) {
|
|
@@ -579,6 +583,7 @@ function linksCommand() {
|
|
|
579
583
|
deadAnchors,
|
|
580
584
|
deadAddresses,
|
|
581
585
|
frontmatterLinks,
|
|
586
|
+
homepageLinks,
|
|
582
587
|
usedManifest,
|
|
583
588
|
} = auditLinks(index);
|
|
584
589
|
|
|
@@ -611,10 +616,24 @@ function linksCommand() {
|
|
|
611
616
|
});
|
|
612
617
|
}
|
|
613
618
|
|
|
619
|
+
// The package homepage. Its addresses are markdown links and
|
|
620
|
+
// `landing:` url/href fields rather than wikilinks — it is
|
|
621
|
+
// published verbatim, so nothing resolves a wikilink on it —
|
|
622
|
+
// and until #54 nothing looked at them at all.
|
|
623
|
+
for (const h of homepageLinks) {
|
|
624
|
+
emitDiagnostic({
|
|
625
|
+
file: h.note.file,
|
|
626
|
+
...positionOfLiteral(h.note.raw, h.text, h.occurrence),
|
|
627
|
+
severity: "error",
|
|
628
|
+
message: `${h.field}: ${h.message}`,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
614
632
|
const failures =
|
|
615
633
|
deadAnchors.length +
|
|
616
634
|
deadAddresses.length +
|
|
617
|
-
frontmatterLinks.length
|
|
635
|
+
frontmatterLinks.length +
|
|
636
|
+
homepageLinks.length;
|
|
618
637
|
if (failures) {
|
|
619
638
|
log.error(
|
|
620
639
|
`${failures} link problem(s) across ${index.notes.length} note(s).`,
|
|
@@ -625,7 +644,8 @@ function linksCommand() {
|
|
|
625
644
|
`${index.notes.length} notes: every anchor link lands ` +
|
|
626
645
|
`and every qualified address resolves ` +
|
|
627
646
|
`(${usedManifest.size} cross-package reference(s) ` +
|
|
628
|
-
`via manifest), no wikilink in frontmatter
|
|
647
|
+
`via manifest), no wikilink in frontmatter, ` +
|
|
648
|
+
`every homepage address resolvable.`,
|
|
629
649
|
);
|
|
630
650
|
}
|
|
631
651
|
} catch (err) {
|
|
@@ -823,7 +843,8 @@ function siteCommand() {
|
|
|
823
843
|
|
|
824
844
|
const s = result.stats;
|
|
825
845
|
log.info(
|
|
826
|
-
`wrote ${s.
|
|
846
|
+
`wrote ${s.homepages ?? 0} homepage(s) + ` +
|
|
847
|
+
`${s.content ?? 0} content page(s) + ` +
|
|
827
848
|
`${s.tree ?? 0} tree page(s) + ${s.landings} ` +
|
|
828
849
|
`landing(s) to ${path.relative(process.cwd(), s.out)}`,
|
|
829
850
|
);
|
package/bin/package-build.mjs
CHANGED
|
@@ -78,7 +78,7 @@ import yargs from "yargs";
|
|
|
78
78
|
import { hideBin } from "yargs/helpers";
|
|
79
79
|
|
|
80
80
|
import { loadPackageBuildConfig } from "../config.mjs";
|
|
81
|
-
import { loadPackConfig } from "../engine/pack-config.mjs";
|
|
81
|
+
import { loadPackConfig, packConfigPath } from "../engine/pack-config.mjs";
|
|
82
82
|
import { cleanBuildArtifacts, stageAssets } from "../stage.mjs";
|
|
83
83
|
import { validateLangSource } from "../lang.mjs";
|
|
84
84
|
import {
|
|
@@ -354,6 +354,8 @@ function manifestCommand() {
|
|
|
354
354
|
artifact: config.artifact,
|
|
355
355
|
outDir: path.join(config.rootDir, config.stageDir),
|
|
356
356
|
flags,
|
|
357
|
+
// So a `packFolders` finding names the line it is about.
|
|
358
|
+
configFile: packConfigPath(),
|
|
357
359
|
});
|
|
358
360
|
console.log(
|
|
359
361
|
`✅ Wrote ${path.relative(config.rootDir, written)} ` +
|