@heroiclands/package-build 22.1.1 → 22.3.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 +18 -0
- package/CONTENT.md +27 -32
- package/bin/content-build.mjs +0 -3
- package/bin/package-build.mjs +9 -26
- package/config.mjs +7 -2
- package/content-config.mjs +73 -41
- package/docs/api.md +26 -25
- package/docs/commands.md +22 -22
- package/docs/configuration.md +78 -46
- package/docs/content-format.md +36 -6
- package/engine/foundry-entries.mjs +12 -1
- package/engine/frontmatter-lint.mjs +2 -3
- package/engine/helpers.mjs +4 -1
- package/engine/ids.mjs +12 -0
- package/engine/note-claims.mjs +21 -0
- package/engine/pack-config.mjs +14 -1
- package/engine/pack-router.mjs +82 -5
- package/engine/pdf-build.mjs +2 -2
- package/engine/retired-fields.mjs +2 -3
- package/engine/schema-check.mjs +6 -6
- package/engine/site-build.mjs +59 -218
- package/engine/site-config.mjs +22 -22
- package/engine/site-index.mjs +18 -62
- package/engine/web-wikilinks.mjs +2 -16
- package/engine/wikilinks.mjs +35 -14
- package/manifest.mjs +16 -10
- package/package.json +1 -1
- package/release.mjs +41 -8
- package/sohl/kb-passes.mjs +14 -85
- package/types/config.d.mts +17 -0
- package/types/content-config.d.mts +6 -6
- package/types/engine/ids.d.mts +11 -0
- package/types/engine/pack-router.d.mts +20 -2
- package/types/engine/schema-check.d.mts +5 -5
- package/types/engine/site-build.d.mts +12 -62
- package/types/engine/site-config.d.mts +6 -8
- package/types/engine/site-index.d.mts +3 -28
- package/types/engine/web-wikilinks.d.mts +1 -1
- package/types/engine/wikilinks.d.mts +6 -3
- package/types/manifest.d.mts +5 -3
- package/types/release.d.mts +7 -6
- package/types/sohl/kb-passes.d.mts +5 -36
package/docs/commands.md
CHANGED
|
@@ -166,7 +166,7 @@ Publish this package's DataModel field sets as `schema.json`.
|
|
|
166
166
|
**SYNOPSIS**
|
|
167
167
|
|
|
168
168
|
```
|
|
169
|
-
package-build schema
|
|
169
|
+
package-build schema
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
**DESCRIPTION**
|
|
@@ -174,23 +174,20 @@ package-build schema [--check]
|
|
|
174
174
|
Publishes the registries named in `packageBuild.schema` as `schema.json`, read
|
|
175
175
|
by `content-build content-format schema` and, in a consuming package, by
|
|
176
176
|
`content-build lint`'s emitted-versus-declared check. A repository that
|
|
177
|
-
declares no registries has nothing to publish.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`
|
|
181
|
-
|
|
177
|
+
declares no registries has nothing to publish. Reads the registries
|
|
178
|
+
`packageBuild.schema` names; writes `build/schema.json`. A release publishes
|
|
179
|
+
it beside the archive and the manifest when `packageBuild.assets` names
|
|
180
|
+
`build/schema.json` (see `package-build release`); a module's
|
|
181
|
+
`content-build deps fetch` keeps the copy from the archive it downloads.
|
|
182
182
|
|
|
183
183
|
**OPTIONS**
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
| --------- | ------- | ------- | ------------------------------------------------------------------------------ |
|
|
187
|
-
| `--check` | boolean | `false` | Fail when the committed `schema.json` is out of date rather than rewriting it. |
|
|
185
|
+
None.
|
|
188
186
|
|
|
189
187
|
**EXIT STATUS**
|
|
190
188
|
|
|
191
|
-
1
|
|
192
|
-
|
|
193
|
-
`packageBuild.schema` is empty, which logs and publishes nothing.
|
|
189
|
+
1 on any thrown error. Otherwise 0 — including when `packageBuild.schema` is
|
|
190
|
+
empty, which logs and publishes nothing.
|
|
194
191
|
|
|
195
192
|
**EXAMPLES**
|
|
196
193
|
|
|
@@ -202,7 +199,7 @@ package-build: no `packageBuild.schema` declared; nothing to publish.
|
|
|
202
199
|
**SEE ALSO**
|
|
203
200
|
|
|
204
201
|
`content-build content-format schema`, `content-build lint [root]`,
|
|
205
|
-
[Configuration](configuration.md).
|
|
202
|
+
`package-build release`, [Configuration](configuration.md).
|
|
206
203
|
|
|
207
204
|
### `package-build manifest`
|
|
208
205
|
|
|
@@ -670,14 +667,17 @@ package-build release [--no-pdf]
|
|
|
670
667
|
|
|
671
668
|
The artifact name comes from `packageKind` — a system ships as
|
|
672
669
|
`system.json`'s sibling, a module as `module.json`'s — so no repository
|
|
673
|
-
states it a second time.
|
|
674
|
-
(`
|
|
675
|
-
`
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
the
|
|
680
|
-
|
|
670
|
+
states it a second time. It also publishes the content index the manifest
|
|
671
|
+
advertises (`flags.metadataUrl`) and, when the stage carries one,
|
|
672
|
+
`schema.json` — a repository that names `build/schema.json` in
|
|
673
|
+
`packageBuild.assets` gets it released beside the archive; one that does not
|
|
674
|
+
gets none. When the package publishes content (`publish.site: content`), it
|
|
675
|
+
also builds the content-tree book (see `content-build pdf`) and reports it
|
|
676
|
+
alongside the archive; `--no-pdf` skips that step for a release that has a
|
|
677
|
+
tree but does not want the book this time. A book that fails to build is
|
|
678
|
+
reported, never fatal — the archive above is the release regardless. Reads
|
|
679
|
+
the staged package and (for the book) the content tree; writes
|
|
680
|
+
`<artifact>.zip` and, unless skipped, the book, both under `build/dist`.
|
|
681
681
|
|
|
682
682
|
**OPTIONS**
|
|
683
683
|
|
|
@@ -701,7 +701,7 @@ $ package-build release
|
|
|
701
701
|
|
|
702
702
|
**SEE ALSO**
|
|
703
703
|
|
|
704
|
-
`content-build pdf`, [Configuration](configuration.md).
|
|
704
|
+
`content-build pdf`, `package-build schema`, [Configuration](configuration.md).
|
|
705
705
|
|
|
706
706
|
### `package-build deploy <stage>`
|
|
707
707
|
|
package/docs/configuration.md
CHANGED
|
@@ -752,28 +752,36 @@ Any other key under `docs.itemFields` is refused:
|
|
|
752
752
|
|
|
753
753
|
**Type:** object · **Optional** · every key defaults to nothing published:
|
|
754
754
|
|
|
755
|
-
| Key | Type | Default
|
|
756
|
-
| ----------------------- | -------- |
|
|
757
|
-
| `site.base` | string | `""`
|
|
758
|
-
| `site.assets` | string | `""`
|
|
759
|
-
| `site.
|
|
760
|
-
| `site.
|
|
761
|
-
| `site.
|
|
762
|
-
| `site.landing` | object | `null`
|
|
763
|
-
| `site.
|
|
764
|
-
| `site.
|
|
765
|
-
| `site.
|
|
766
|
-
| `site.
|
|
767
|
-
| `site.
|
|
768
|
-
| `site.
|
|
769
|
-
| `site.hugo` | object | `{}` |
|
|
755
|
+
| Key | Type | Default |
|
|
756
|
+
| ----------------------- | -------- | ------------------------------------------- |
|
|
757
|
+
| `site.base` | string | `""` |
|
|
758
|
+
| `site.assets` | string | `""`, but required for `content-build site` |
|
|
759
|
+
| `site.description` | string | `""`, but required for `content-build site` |
|
|
760
|
+
| `site.packages` | string[] | `[]` |
|
|
761
|
+
| `site.sections` | object | `{}` |
|
|
762
|
+
| `site.landing` | object | `null` |
|
|
763
|
+
| `site.pass` | string | `""` |
|
|
764
|
+
| `site.passOptions` | object | `{}` |
|
|
765
|
+
| `site.backfillSections` | boolean | `false` |
|
|
766
|
+
| `site.list` | object | `{shortcodes: false}` |
|
|
767
|
+
| `site.notfound` | object | `null` |
|
|
768
|
+
| `site.hugo` | object | `{}` |
|
|
770
769
|
|
|
771
770
|
How much of a package reaches the web at all is **not** here — it is
|
|
772
771
|
[`publish.site`](#publish). `site` is framing: what a section is called,
|
|
773
|
-
which
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
772
|
+
which named pass bundle supplies the repository's own body rewrites, and the
|
|
773
|
+
residue of the [generated Hugo configuration](#the-generated-hugo-configuration)
|
|
774
|
+
that is genuinely this repository's own.
|
|
775
|
+
|
|
776
|
+
What the site publishes is the content tree, and nothing beside it. A page of
|
|
777
|
+
documentation is a note — `type: doc`, addressed by its shortcode, and
|
|
778
|
+
`pack: none` where it compiles into no Foundry document — so there is no
|
|
779
|
+
second mechanism for mounting a directory of markdown, and a configuration
|
|
780
|
+
that names one is refused with a message saying where the page goes instead:
|
|
781
|
+
|
|
782
|
+
> ``package-build config: `site.trees` is retired — a page is a note in the content tree. Give each page `type: doc`, a `shortcode` and `pack: none`, file it under `assets/content/`, and declare the section that lists it under `site.sections`.``
|
|
783
|
+
|
|
784
|
+
> ``package-build config: `site.readmeSections` is retired — a page is a note in the content tree. Give each page `type: doc`, a `shortcode` and `pack: none`, file it under `assets/content/`, and declare the section that lists it under `site.sections`.``
|
|
777
785
|
|
|
778
786
|
Where the Hugo tree is written is not a choice. `content-build site` writes
|
|
779
787
|
the whole Hugo source tree under `build/hugo/` — the generated `hugo.toml`,
|
|
@@ -785,7 +793,7 @@ published. A `site.out` is refused by name:
|
|
|
785
793
|
|
|
786
794
|
> ``package-build config: `site` must be a mapping.``
|
|
787
795
|
|
|
788
|
-
> ``package-build config: `site.<key>` is not a recognized option (expected one of: base, assets, packages, sections,
|
|
796
|
+
> ``package-build config: `site.<key>` is not a recognized option (expected one of: base, assets, description, packages, sections, landing, pass, passOptions, backfillSections, list, notfound, hugo).``
|
|
789
797
|
|
|
790
798
|
`site.assets` is the host every package's imagery is served from, and it is
|
|
791
799
|
the one address in this file that is not this repository's own. A note names
|
|
@@ -798,11 +806,30 @@ Absolute, and the trailing slash is trimmed:
|
|
|
798
806
|
|
|
799
807
|
> ``package-build config: `site.assets` must be an absolute `http://` or `https://` address — it is the host every package's imagery is served from, and a relative value resolves against whichever page happens to carry the image.``
|
|
800
808
|
|
|
809
|
+
`content-build site` refuses to generate a configuration with no
|
|
810
|
+
`site.assets` at all — there is no defensible default, because the theme
|
|
811
|
+
resolves every relative asset against it:
|
|
812
|
+
|
|
813
|
+
> ``package-build config: `site.assets` is not declared, and a site build needs one — it is the host every package's imagery is served from, and the theme resolves every relative asset against it.``
|
|
814
|
+
|
|
801
815
|
The generated Hugo configuration carries the same host as
|
|
802
816
|
`params.cdnBaseURL`, which the theme resolves a relative asset path against.
|
|
803
817
|
The two are one value read by two readers: the toolchain emits it into a
|
|
804
818
|
page, and the theme joins it onto anything the toolchain left relative.
|
|
805
819
|
|
|
820
|
+
`site.description` is the site's `<meta name="description">` — one plain
|
|
821
|
+
sentence, distinct from the Foundry package browser's pitch
|
|
822
|
+
([`packageBuild.manifest.descriptionHtml`](#packagebuildmanifest), which
|
|
823
|
+
allows HTML). Required for `content-build site`, the way `packageBuild.manifest.title`
|
|
824
|
+
is:
|
|
825
|
+
|
|
826
|
+
> ``package-build config: `site.description` is not declared, and the site's `<meta name="description">` reads from it.``
|
|
827
|
+
|
|
828
|
+
Markup belongs in `descriptionHtml`, not here — a value containing `<` is
|
|
829
|
+
refused:
|
|
830
|
+
|
|
831
|
+
> ``package-build config: `site.description` contains `<` — this is plain text for the site's `<meta name="description">`; markup belongs in `packageBuild.manifest.descriptionHtml`.``
|
|
832
|
+
|
|
806
833
|
`site.packages` names which content packages' notes the site walks, beyond
|
|
807
834
|
this one's own; `site.pass` names a repository's own body-rewrite bundle
|
|
808
835
|
(the one part of the site contract that is code, exactly as `itemBuilders`
|
|
@@ -812,25 +839,8 @@ names a registry):
|
|
|
812
839
|
|
|
813
840
|
> ``package-build config: `site.packages[<index>]` must be a non-empty string.``
|
|
814
841
|
|
|
815
|
-
`site.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
| Key (under `site.trees[]`) | Type | Required |
|
|
819
|
-
| -------------------------- | ------ | -------- |
|
|
820
|
-
| `site.trees[].from` | string | yes |
|
|
821
|
-
| `site.trees[].section` | string | yes |
|
|
822
|
-
|
|
823
|
-
> ``package-build config: `site.trees` must be a list.``
|
|
824
|
-
|
|
825
|
-
> ``package-build config: `site.trees[<index>]` must be a mapping.``
|
|
826
|
-
|
|
827
|
-
> ``package-build config: `site.trees[<index>].from` must be a non-empty string.``
|
|
828
|
-
|
|
829
|
-
> ``package-build config: `site.trees[<index>].<key>` is not a recognized option (expected one of: from, section).``
|
|
830
|
-
|
|
831
|
-
`site.sections` (and `site.readmeSections`, the same shape) is a closed
|
|
832
|
-
vocabulary — a section's _only_ place to speak, since it exists solely as
|
|
833
|
-
the generated `_index.md` this build writes for it:
|
|
842
|
+
`site.sections` is a closed vocabulary — a section's _only_ place to speak,
|
|
843
|
+
since it exists solely as the generated `_index.md` this build writes for it:
|
|
834
844
|
|
|
835
845
|
| Key (under `site.sections.<name>`) | Type | Required | Default |
|
|
836
846
|
| ---------------------------------- | -------------------------- | -------- | ------- |
|
|
@@ -968,9 +978,9 @@ in it has one source, and that source is where it is edited:
|
|
|
968
978
|
| `disableKinds` | whether any note in the tree carries `tags:`, which the site walk discovers: `["taxonomy", "term", "RSS"]` when none does, `["RSS"]` when at least one does |
|
|
969
979
|
| `taxonomies` | the same fact — written as `{ tag = "tags" }` when at least one note carries `tags:`, absent otherwise |
|
|
970
980
|
| `outputs` | the same fact — written as `{ taxonomy = ["HTML"], term = ["HTML"] }` when at least one note carries `tags:`, absent otherwise |
|
|
971
|
-
| `params.description` | `
|
|
981
|
+
| `params.description` | `site.description`, which is required |
|
|
972
982
|
| `params.author` | `package.json` `author`, its `name`; absent when the package declares none |
|
|
973
|
-
| `params.cdnBaseURL` | `site.assets
|
|
983
|
+
| `params.cdnBaseURL` | `site.assets`, which is required |
|
|
974
984
|
| `params.brand` | the organisation's brand links — `logo`, `licenseURL`, `discordURL` — in `engine/site-config.mjs` |
|
|
975
985
|
| `params.list` | `site.list` |
|
|
976
986
|
| `params.notfound` | `site.notfound`; absent when undeclared |
|
|
@@ -991,6 +1001,17 @@ none fails the site build:
|
|
|
991
1001
|
|
|
992
1002
|
> ``package-build config: `packageBuild.manifest.title` is not declared, and the site's `title` reads from it.``
|
|
993
1003
|
|
|
1004
|
+
`params.description` reads from `site.description` the same way, and fails
|
|
1005
|
+
the same way when it is absent:
|
|
1006
|
+
|
|
1007
|
+
> ``package-build config: `site.description` is not declared, and the site's `<meta name="description">` reads from it.``
|
|
1008
|
+
|
|
1009
|
+
`params.cdnBaseURL` reads from `site.assets`, and the theme resolves every
|
|
1010
|
+
relative asset against it, so a configuration declaring none fails the site
|
|
1011
|
+
build the same way:
|
|
1012
|
+
|
|
1013
|
+
> ``package-build config: `site.assets` is not declared, and a site build needs one — it is the host every package's imagery is served from, and the theme resolves every relative asset against it.``
|
|
1014
|
+
|
|
994
1015
|
Nothing else is emitted. A site whose notes carry no `tags:` publishes no
|
|
995
1016
|
taxonomy pages — `[taxonomies]` and `[outputs]` go unwritten, and Hugo's
|
|
996
1017
|
defaults never apply because `taxonomy` and `term` are disabled kinds. A site
|
|
@@ -1305,9 +1326,9 @@ its content tree's addresses mount inside the package.
|
|
|
1305
1326
|
Every HeroicLands package publishes at least an authored homepage at
|
|
1306
1327
|
`https://www.heroiclands.org/<contentPackage>/` — there is no value meaning
|
|
1307
1328
|
_no web presence at all_. `homepage` is the floor: the authored homepage
|
|
1308
|
-
and nothing else, no content-tree walk, no `site.sections` / `site.
|
|
1309
|
-
|
|
1310
|
-
|
|
1329
|
+
and nothing else, no content-tree walk, no `site.sections` / `site.landing`
|
|
1330
|
+
output. `content` is the homepage plus every page the content tree
|
|
1331
|
+
publishes. `publishesContentPages(config)`, exported from
|
|
1311
1332
|
`content-config.mjs` alongside [`compilesFoundryDocuments`](#packagekind),
|
|
1312
1333
|
answers the one question every reader of the mode actually asks — the site
|
|
1313
1334
|
build, to decide whether to walk the tree at all, and the content index, to
|
|
@@ -1436,11 +1457,22 @@ that has a wrong answer rather than an unknown one: a key the build
|
|
|
1436
1457
|
silently overwritten and the two would be free to disagree with nothing to
|
|
1437
1458
|
say so.
|
|
1438
1459
|
|
|
1460
|
+
`packageBuild.manifest.descriptionHtml` is the exception worth calling out on
|
|
1461
|
+
its own: it is not forbidden, it **is** how `description` is authored. It is
|
|
1462
|
+
the pitch Foundry's package browser shows — HTML allowed, any length — and it
|
|
1463
|
+
is emitted into the generated manifest as `description`; the key itself never
|
|
1464
|
+
survives into the manifest under its own name. `package.json`'s own
|
|
1465
|
+
`description` is read by neither this nor the site (see
|
|
1466
|
+
[`site.description`](#site)) — a declared one is reported as a warning naming
|
|
1467
|
+
both real keys, so it cannot drift back into use:
|
|
1468
|
+
|
|
1469
|
+
> `package.json: warning: \`description\` is read by nothing; the Foundry pitch is \`packageBuild.manifest.descriptionHtml\` and the site's is \`site.description\`` — a JSON manifest carries no line to point at, so only the file is named.
|
|
1470
|
+
|
|
1439
1471
|
| Forbidden key | Derived from |
|
|
1440
1472
|
| ------------------------------------- | ---------------------------------------------------------------- |
|
|
1441
1473
|
| `packageBuild.manifest.id` | `foundryPackage`, itself derived from `package.json` `name` |
|
|
1442
1474
|
| `packageBuild.manifest.version` | `package.json` `version` |
|
|
1443
|
-
| `packageBuild.manifest.description` | `
|
|
1475
|
+
| `packageBuild.manifest.description` | `packageBuild.manifest.descriptionHtml` |
|
|
1444
1476
|
| `packageBuild.manifest.url` | `package.json` `repository` |
|
|
1445
1477
|
| `packageBuild.manifest.bugs` | `package.json` `repository` |
|
|
1446
1478
|
| `packageBuild.manifest.manifest` | `package.json` `repository` and the release tag |
|
|
@@ -1451,7 +1483,7 @@ say so.
|
|
|
1451
1483
|
|
|
1452
1484
|
> ``package-build config: `packageBuild.manifest.version` is derived from package.json `version` and must not be declared — it would be overwritten, and the two would disagree with nothing to say so.``
|
|
1453
1485
|
|
|
1454
|
-
> ``package-build config: `packageBuild.manifest.description` is derived from
|
|
1486
|
+
> ``package-build config: `packageBuild.manifest.description` is derived from `packageBuild.manifest.descriptionHtml` and must not be declared — it would be overwritten, and the two would disagree with nothing to say so.``
|
|
1455
1487
|
|
|
1456
1488
|
> ``package-build config: `packageBuild.manifest` must be a mapping.``
|
|
1457
1489
|
|
package/docs/content-format.md
CHANGED
|
@@ -801,13 +801,43 @@ hm3:
|
|
|
801
801
|
Where no pack of that type is the default, the build refuses rather than
|
|
802
802
|
guessing, and names the candidates.
|
|
803
803
|
|
|
804
|
-
|
|
804
|
+
**`pack: none` compiles the note into no document.** The note is walked,
|
|
805
|
+
published as a page, present in the content index with an address and no
|
|
806
|
+
Foundry UUID, and addressable by wikilink like any other; every pack compiler
|
|
807
|
+
passes over it without a finding, and the check that reports a note nothing
|
|
808
|
+
compiles says nothing about it. Write it on a page that belongs on the website
|
|
809
|
+
and in no compendium — a page of developer documentation, a page about the
|
|
810
|
+
package itself:
|
|
805
811
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
812
|
+
```yaml
|
|
813
|
+
type: doc
|
|
814
|
+
subType: concept
|
|
815
|
+
shortcode: architecture
|
|
816
|
+
pack: none
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
A link to such a note resolves everywhere the note is addressable. On the web
|
|
820
|
+
it is an ordinary link to the page. In a compiled journal there is no document
|
|
821
|
+
to open, so the reader gets the link's text as prose and no `@UUID` — the
|
|
822
|
+
mirror of a link into a package that publishes documents and no pages.
|
|
823
|
+
|
|
824
|
+
It is accepted only on a type whose **sole document is the JournalEntry its
|
|
825
|
+
prose becomes** — `doc`, `place`, `lore` and `scenario`. On a type that
|
|
826
|
+
compiles an Item, an Actor, a Macro, a Scene or an Adventure it is refused by
|
|
827
|
+
name, because there it would drop the document the type exists to produce;
|
|
828
|
+
the message names that document. It is read per system like any other
|
|
829
|
+
`pack:`, so `<system>.pack: none` withholds one system's document while the
|
|
830
|
+
shared declaration names a pack for the rest, and a configured pack may not
|
|
831
|
+
be called `none`.
|
|
832
|
+
|
|
833
|
+
Four declarations are refused, each with the reason:
|
|
834
|
+
|
|
835
|
+
| written | why it is refused |
|
|
836
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
837
|
+
| a **companion** pack | A companion is written by another pack's pass, so no note may be routed into one. |
|
|
838
|
+
| a pack **nothing answers to** | The message lists the configured packs of that document type. |
|
|
839
|
+
| a pack of **another document type** | A note's `pack:` names a pack of its own document type. |
|
|
840
|
+
| **`none`** on a type with a second document | The Item, Actor, Macro, Scene or Adventure the type compiles into would be dropped, by name. |
|
|
811
841
|
|
|
812
842
|
#### Template priority: which template wins
|
|
813
843
|
|
|
@@ -70,6 +70,7 @@ import { resolveNoteId } from "./note-ids.mjs";
|
|
|
70
70
|
import { compendiumUuid, currentType, packForType, pageUuid } from "./ids.mjs";
|
|
71
71
|
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
72
72
|
import { isHomepage } from "./homepage.mjs";
|
|
73
|
+
import { declaresNoPack } from "./pack-router.mjs";
|
|
73
74
|
import { assertNoDeclaredPackage } from "./note-package.mjs";
|
|
74
75
|
import { assertNoDeclaredFolder } from "./folder-notes.mjs";
|
|
75
76
|
import {
|
|
@@ -179,7 +180,17 @@ export function entriesForNote(fm, name, address, body, ctx) {
|
|
|
179
180
|
// and its id is hashed under the `folder` namespace against its own
|
|
180
181
|
// address rather than under `document`. Emitting one would publish an
|
|
181
182
|
// `Item` UUID for a `Folder`, at an id no document carries.
|
|
182
|
-
|
|
183
|
+
//
|
|
184
|
+
// And a note declaring **`pack: none`** is in no pack by its own
|
|
185
|
+
// statement: it publishes a page and compiles into nothing, so it has
|
|
186
|
+
// an address and no UUID. The shared declaration is what is read,
|
|
187
|
+
// because an entry names one document and this pass routes no system.
|
|
188
|
+
(
|
|
189
|
+
id &&
|
|
190
|
+
!NEVER_PACKED_TYPES.has(String(type)) &&
|
|
191
|
+
!DERIVED_PACKED_TYPES.has(String(type)) &&
|
|
192
|
+
!(routeFm && declaresNoPack(routeFm))
|
|
193
|
+
) ?
|
|
183
194
|
compendiumUuid(
|
|
184
195
|
foundryPackageId,
|
|
185
196
|
type,
|
|
@@ -1265,9 +1265,8 @@ export function lintNote(
|
|
|
1265
1265
|
message: draftRetiredMessage(),
|
|
1266
1266
|
});
|
|
1267
1267
|
}
|
|
1268
|
-
// Anchored at column 1 for the same reason `aliases` is:
|
|
1269
|
-
//
|
|
1270
|
-
// some other block is not this field.
|
|
1268
|
+
// Anchored at column 1 for the same reason `aliases` is: a nested
|
|
1269
|
+
// `section` under some other block is not this field.
|
|
1271
1270
|
if (Object.hasOwn(fm, "section")) {
|
|
1272
1271
|
findings.push({
|
|
1273
1272
|
file: note.file,
|
package/engine/helpers.mjs
CHANGED
|
@@ -38,7 +38,7 @@ import { foundryAddressProblem, pathnameProblem, resolvePathname } from "./pathn
|
|
|
38
38
|
import log from "loglevel";
|
|
39
39
|
|
|
40
40
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
41
|
-
import { packRouter } from "./pack-router.mjs";
|
|
41
|
+
import { declaresNoPack, packRouter } from "./pack-router.mjs";
|
|
42
42
|
import { contentPackage, foundryPackageId } from "./content-package.mjs";
|
|
43
43
|
import { searchableFrontmatter } from "./note-package.mjs";
|
|
44
44
|
import { PACKAGE_BASE } from "./content-address.mjs";
|
|
@@ -741,6 +741,9 @@ export function buildContentLinkIndex(
|
|
|
741
741
|
// its prose compiles into lands — two documents, two packs.
|
|
742
742
|
pack: router.resolveOrNull(fm, packForType(fm.type).docType),
|
|
743
743
|
docPack: router.resolveOrNull(fm, "JournalEntry"),
|
|
744
|
+
// Whether the note declares `pack: none` and so has no document
|
|
745
|
+
// at all: a link to it names a page, never a compendium entry.
|
|
746
|
+
none: declaresNoPack(fm),
|
|
744
747
|
shortcode: fm.shortcode ?? null,
|
|
745
748
|
// What the note *is*, carried so a caller resolving a reference
|
|
746
749
|
// can group by the family its target declares rather than only by
|
package/engine/ids.mjs
CHANGED
|
@@ -86,6 +86,18 @@ export const MAP_SUBTYPES = Object.freeze(["battlemap", "localmap", "regionalmap
|
|
|
86
86
|
*/
|
|
87
87
|
export const JOURNAL_TYPES = Object.freeze(new Set(["doc", "place", "lore", "scenario"]));
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* The `pack:` value that routes a note's document into no compendium.
|
|
91
|
+
*
|
|
92
|
+
* One reading, on the key that already decides which compendium receives a
|
|
93
|
+
* note: the note publishes to the site and compiles into nothing. Declared
|
|
94
|
+
* here, beside the pack table, so the configuration can refuse a pack of this
|
|
95
|
+
* name and the router can read the value from one spelling.
|
|
96
|
+
*
|
|
97
|
+
* @type {string}
|
|
98
|
+
*/
|
|
99
|
+
export const NO_PACK = "none";
|
|
100
|
+
|
|
89
101
|
/**
|
|
90
102
|
* Content type → the pack its documents compile into, and the document type
|
|
91
103
|
* that pack holds.
|
package/engine/note-claims.mjs
CHANGED
|
@@ -91,6 +91,7 @@ import { JOURNAL_TYPES, MAP_TYPES, PACK_BY_TYPE, RETIRED_TYPES, currentType } fr
|
|
|
91
91
|
import { itemTypes } from "./item-registry.mjs";
|
|
92
92
|
import { docEntryTypes } from "./item-docs.mjs";
|
|
93
93
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
94
|
+
import { declaresNoPack } from "./pack-router.mjs";
|
|
94
95
|
import { locateFrontmatterKey } from "./retired-fields.mjs";
|
|
95
96
|
import { noteTypesFor, subtypeRow } from "./document-subtypes.mjs";
|
|
96
97
|
import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
|
|
@@ -641,6 +642,12 @@ export function unclaimedNoteFindings(config = loadPackConfig(), sources, { reco
|
|
|
641
642
|
// {@link claimedNoteTypes} states: its JSON is checked in, it has no pass,
|
|
642
643
|
// and no note is routed into it.
|
|
643
644
|
const configured = new Set((config.packs ?? []).filter((p) => !p.prebuilt).map((p) => p.type));
|
|
645
|
+
// Every system a pack declares, plus the shared position: a note that
|
|
646
|
+
// answers `pack: none` for all of them compiles into nothing anywhere.
|
|
647
|
+
const routed = [
|
|
648
|
+
undefined,
|
|
649
|
+
...new Set((config.packs ?? []).map((p) => p.system).filter(Boolean)),
|
|
650
|
+
];
|
|
644
651
|
|
|
645
652
|
// The corpus this compile derived once, required rather than
|
|
646
653
|
// derived here: this module is imported *by* the content index, so it
|
|
@@ -681,6 +688,20 @@ export function unclaimedNoteFindings(config = loadPackConfig(), sources, { reco
|
|
|
681
688
|
// Every document it produces has somewhere to go.
|
|
682
689
|
if (produces.length && !missing.length) continue;
|
|
683
690
|
|
|
691
|
+
// `pack: none`, on a type whose only document is the JournalEntry its
|
|
692
|
+
// prose becomes: the note compiles into nothing **by declaration**, so
|
|
693
|
+
// a missing JournalEntry pack drops nothing it meant to keep. A type
|
|
694
|
+
// that also produces an Item or an Actor is not excused here — the
|
|
695
|
+
// router refuses that declaration by name when a pass asks it, and
|
|
696
|
+
// where no pass does, the finding below still says nothing claims the
|
|
697
|
+
// note.
|
|
698
|
+
if (
|
|
699
|
+
produces.every((docType) => docType === "JournalEntry") &&
|
|
700
|
+
routed.every((system) => declaresNoPack(fm, system))
|
|
701
|
+
) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
|
|
684
705
|
// Some do and some do not: the note compiles, and one of its documents
|
|
685
706
|
// is dropped in silence. A type nothing produces at all falls past this
|
|
686
707
|
// to the unclaimed messages below, where `produces` being empty is itself
|
package/engine/pack-config.mjs
CHANGED
|
@@ -570,9 +570,22 @@ export function configFromData(data, configPath) {
|
|
|
570
570
|
);
|
|
571
571
|
}
|
|
572
572
|
}
|
|
573
|
-
const { pkg } = readPackageJson(rootDir);
|
|
573
|
+
const { manifestPath, pkg } = readPackageJson(rootDir);
|
|
574
574
|
if (pkg.homepage !== undefined) input.homepage = pkg.homepage;
|
|
575
575
|
if (pkg.author !== undefined) input.author = pkg.author;
|
|
576
|
+
// `description` is npm metadata nothing displays for a private package —
|
|
577
|
+
// read by neither the manifest nor the site, so a declared one is a
|
|
578
|
+
// warning rather than a silent no-op that looks like it did something.
|
|
579
|
+
if (pkg.description !== undefined) {
|
|
580
|
+
emitDiagnostic({
|
|
581
|
+
file: manifestPath,
|
|
582
|
+
severity: "warning",
|
|
583
|
+
message:
|
|
584
|
+
"`description` is read by nothing; the Foundry pitch is " +
|
|
585
|
+
"`packageBuild.manifest.descriptionHtml` and the site's is " +
|
|
586
|
+
"`site.description`",
|
|
587
|
+
});
|
|
588
|
+
}
|
|
576
589
|
|
|
577
590
|
if (input.itemBuilders !== undefined) {
|
|
578
591
|
const declared = input.itemBuilders;
|
package/engine/pack-router.mjs
CHANGED
|
@@ -65,11 +65,22 @@
|
|
|
65
65
|
* a shared value that cannot describe a system-specific pack, so it does not
|
|
66
66
|
* answer and that system falls through to its own default. Both were silent
|
|
67
67
|
* losses of one system's whole document set.
|
|
68
|
+
* - **`pack: none` routes the document nowhere, on purpose.** The note is
|
|
69
|
+
* walked, indexed, published and linkable, and no compendium receives it:
|
|
70
|
+
* {@link createPackRouter}'s `resolve` answers `undefined`, which no pass's
|
|
71
|
+
* name equals, so every pass passes over the note as one it does not own.
|
|
72
|
+
* It is accepted only on a type whose sole document is the JournalEntry its
|
|
73
|
+
* prose becomes — `doc` and the other journal-only types. On a type that
|
|
74
|
+
* compiles an Item, an Actor, a Macro, a Scene or an Adventure it is refused
|
|
75
|
+
* by name, because there the declaration would drop the document the type
|
|
76
|
+
* exists to produce. Read per system like any other `pack:`, so a block may
|
|
77
|
+
* say `none` for one system and the shared declaration may name a pack for
|
|
78
|
+
* the rest.
|
|
68
79
|
*
|
|
69
80
|
* @module
|
|
70
81
|
*/
|
|
71
82
|
|
|
72
|
-
import { packForType } from "./ids.mjs";
|
|
83
|
+
import { NO_PACK, packForType } from "./ids.mjs";
|
|
73
84
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
74
85
|
import { blockProperty, systemBlock } from "./system-block.mjs";
|
|
75
86
|
|
|
@@ -95,6 +106,35 @@ export class PackRoutingError extends Error {
|
|
|
95
106
|
*/
|
|
96
107
|
export const PACK_FIELD = "pack";
|
|
97
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The `pack:` value that routes a note's document into no compendium — see
|
|
111
|
+
* {@link module:engine/ids.NO_PACK}. Re-exported here because this is the
|
|
112
|
+
* module that reads it; a configured pack may not take the name, so the value
|
|
113
|
+
* can never be mistaken for one.
|
|
114
|
+
*/
|
|
115
|
+
export { NO_PACK };
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether a note declares `pack: none` for one system's document.
|
|
119
|
+
*
|
|
120
|
+
* The same reading {@link createPackRouter}'s `resolve` applies: the system's
|
|
121
|
+
* block wins where it declares a pack, and the shared top-level value stands
|
|
122
|
+
* otherwise. Asked with no system, it reads the shared value alone, which is
|
|
123
|
+
* what every single-system build and the content index read.
|
|
124
|
+
*
|
|
125
|
+
* Pure, so the readers that never route — the Foundry-address pass, the link
|
|
126
|
+
* index, the unclaimed-type check — can ask it without a router.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} fm - The note's frontmatter.
|
|
129
|
+
* @param {string} [system] - The system whose document is asked about.
|
|
130
|
+
* @returns {boolean} True when the answer for that document is `none`.
|
|
131
|
+
*/
|
|
132
|
+
export function declaresNoPack(fm, system) {
|
|
133
|
+
const declared =
|
|
134
|
+
system === undefined ? fm?.[PACK_FIELD] : blockProperty(fm, system, PACK_FIELD);
|
|
135
|
+
return declared === NO_PACK;
|
|
136
|
+
}
|
|
137
|
+
|
|
98
138
|
/**
|
|
99
139
|
* Build the router for one configured pack list.
|
|
100
140
|
*
|
|
@@ -103,7 +143,7 @@ export const PACK_FIELD = "pack";
|
|
|
103
143
|
*
|
|
104
144
|
* @param {readonly object[]} packs - The resolved `packs` list from
|
|
105
145
|
* `defineConfig`.
|
|
106
|
-
* @returns {{resolve: (fm: object, docType: string, system?: string) => string,
|
|
146
|
+
* @returns {{resolve: (fm: object, docType: string, system?: string) => string|undefined,
|
|
107
147
|
* resolveOrNull: (fm: object, docType: string, system?: string) => string|undefined,
|
|
108
148
|
* packsOfType: (docType: string) => string[],
|
|
109
149
|
* defaultOf: (docType: string) => string|undefined}} The router.
|
|
@@ -179,6 +219,14 @@ export function createPackRouter(packs) {
|
|
|
179
219
|
/** @param {object} fm */
|
|
180
220
|
const noteLabel = (fm) => fm?.name?.full ?? fm?.shortcode ?? fm?.id ?? "a note";
|
|
181
221
|
|
|
222
|
+
/**
|
|
223
|
+
* `"a"` or `"an"`, so a document class reads as English in a message.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} word - The word the article precedes.
|
|
226
|
+
* @returns {string} The article.
|
|
227
|
+
*/
|
|
228
|
+
const article = (word) => (/^[AEIOUaeiou]/.test(word) ? "an" : "a");
|
|
229
|
+
|
|
182
230
|
/**
|
|
183
231
|
* The pack a note declaring none is routed to.
|
|
184
232
|
*
|
|
@@ -212,8 +260,11 @@ export function createPackRouter(packs) {
|
|
|
212
260
|
* @param {string} [system] - The system whose document is being routed. Its
|
|
213
261
|
* block's `pack:` wins over the shared one; without it only the shared
|
|
214
262
|
* declaration is read, which is every single-system build.
|
|
215
|
-
* @returns {string} The pack name
|
|
216
|
-
*
|
|
263
|
+
* @returns {string|undefined} The pack name — or `undefined` where the
|
|
264
|
+
* note declares `pack: none`, which no pass's name equals, so every pass
|
|
265
|
+
* passes over the note as one it does not own.
|
|
266
|
+
* @throws {PackRoutingError} When the note routes nowhere, or declares
|
|
267
|
+
* `pack: none` on a type that compiles a document beside its prose.
|
|
217
268
|
*/
|
|
218
269
|
function resolve(fm, docType, system) {
|
|
219
270
|
const inBlock = system === undefined ? undefined : systemBlock(fm, system)?.[PACK_FIELD];
|
|
@@ -225,6 +276,29 @@ export function createPackRouter(packs) {
|
|
|
225
276
|
// JournalEntry — is not what the author was addressing.
|
|
226
277
|
const ownDocType = packForType(fm?.type).docType;
|
|
227
278
|
|
|
279
|
+
// `none`: the note compiles into no document. Only a type whose own
|
|
280
|
+
// document *is* the JournalEntry may say so — for every other type
|
|
281
|
+
// the declaration would drop the Item, Actor, Macro, Scene or
|
|
282
|
+
// Adventure the type exists to produce, and its prose with it. The
|
|
283
|
+
// document class comes from the same table every pass routes by, so
|
|
284
|
+
// the refusal names what the compile would have written. Asked only
|
|
285
|
+
// by the pass that writes the note's own document, as every `pack:`
|
|
286
|
+
// is, so the refusal is reported once rather than once per pass.
|
|
287
|
+
if (declared === NO_PACK && docType === ownDocType) {
|
|
288
|
+
if (ownDocType !== "JournalEntry") {
|
|
289
|
+
const spelled = authoredInBlock ? `${system}.pack: ${NO_PACK}` : `pack: ${NO_PACK}`;
|
|
290
|
+
throw new PackRoutingError(
|
|
291
|
+
`${noteLabel(fm)} declares \`${spelled}\`, but a ${fm?.type} note ` +
|
|
292
|
+
`compiles into ${article(ownDocType)} ${ownDocType}, and ` +
|
|
293
|
+
`\`pack: ${NO_PACK}\` would drop it. Only a type whose sole ` +
|
|
294
|
+
`document is the JournalEntry its prose becomes may ` +
|
|
295
|
+
`declare it — name the pack the ${ownDocType} goes to, or ` +
|
|
296
|
+
`leave \`pack:\` unset for the default.`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
228
302
|
// **A declaration naming another system's pack is not this system's
|
|
229
303
|
// answer**, and which of two things that means depends on where
|
|
230
304
|
// it was written.
|
|
@@ -328,7 +402,10 @@ export function createPackRouter(packs) {
|
|
|
328
402
|
* @param {object} fm - The note's frontmatter.
|
|
329
403
|
* @param {string} docType - The document type being addressed.
|
|
330
404
|
* @param {string} [system] - The system whose document is addressed.
|
|
331
|
-
* @returns {string|undefined} The pack name, or `undefined
|
|
405
|
+
* @returns {string|undefined} The pack name, or `undefined` — for a
|
|
406
|
+
* note that routes nowhere and for one declaring `pack: none`
|
|
407
|
+
* alike. A caller that must tell the two apart asks
|
|
408
|
+
* {@link declaresNoPack}.
|
|
332
409
|
*/
|
|
333
410
|
resolveOrNull(fm, docType, system) {
|
|
334
411
|
try {
|