@heroiclands/package-build 22.1.0 → 22.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/CONTENT.md +22 -0
- package/bin/content-build.mjs +29 -3
- package/bin/package-build.mjs +9 -26
- package/config.mjs +7 -2
- package/content-config.mjs +61 -2
- package/docs/api.md +26 -23
- package/docs/commands.md +22 -22
- package/docs/configuration.md +117 -54
- package/engine/content-links.mjs +19 -9
- package/engine/helpers.mjs +2 -1
- package/engine/metadata-index.mjs +32 -0
- package/engine/note-vocabulary.mjs +20 -0
- package/engine/pack-config.mjs +14 -1
- package/engine/schema-check.mjs +6 -6
- package/engine/site-build.mjs +14 -3
- package/engine/site-config.mjs +68 -27
- package/engine/site-index.mjs +10 -1
- package/engine/web-wikilinks.mjs +16 -6
- package/engine/wikilink-syntax.mjs +10 -0
- package/engine/wikilinks.mjs +34 -7
- package/manifest.mjs +16 -10
- package/package.json +1 -1
- package/release.mjs +41 -8
- package/types/config.d.mts +17 -0
- package/types/content-config.d.mts +11 -0
- package/types/engine/content-links.d.mts +3 -2
- package/types/engine/metadata-index.d.mts +22 -0
- package/types/engine/note-vocabulary.d.mts +12 -0
- package/types/engine/schema-check.d.mts +5 -5
- package/types/engine/site-build.d.mts +5 -1
- package/types/engine/site-config.d.mts +23 -12
- package/types/engine/site-index.d.mts +6 -1
- package/types/engine/web-wikilinks.d.mts +9 -5
- package/types/engine/wikilink-syntax.d.mts +3 -0
- package/types/engine/wikilinks.d.mts +16 -5
- package/types/manifest.d.mts +5 -3
- package/types/release.d.mts +7 -6
package/docs/configuration.md
CHANGED
|
@@ -752,21 +752,22 @@ 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.
|
|
763
|
-
| `site.
|
|
764
|
-
| `site.
|
|
765
|
-
| `site.
|
|
766
|
-
| `site.
|
|
767
|
-
| `site.
|
|
768
|
-
| `site.
|
|
769
|
-
| `site.
|
|
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.readmeSections` | object | `{}` |
|
|
763
|
+
| `site.landing` | object | `null` |
|
|
764
|
+
| `site.trees` | array | `[]` |
|
|
765
|
+
| `site.pass` | string | `""` |
|
|
766
|
+
| `site.passOptions` | object | `{}` |
|
|
767
|
+
| `site.backfillSections` | boolean | `false` |
|
|
768
|
+
| `site.list` | object | `{shortcodes: false}` |
|
|
769
|
+
| `site.notfound` | object | `null` |
|
|
770
|
+
| `site.hugo` | object | `{}` |
|
|
770
771
|
|
|
771
772
|
How much of a package reaches the web at all is **not** here — it is
|
|
772
773
|
[`publish.site`](#publish). `site` is framing: what a section is called,
|
|
@@ -785,7 +786,7 @@ published. A `site.out` is refused by name:
|
|
|
785
786
|
|
|
786
787
|
> ``package-build config: `site` must be a mapping.``
|
|
787
788
|
|
|
788
|
-
> ``package-build config: `site.<key>` is not a recognized option (expected one of: base, assets, packages, sections, readmeSections, landing, trees, pass, passOptions, backfillSections, list, notfound, hugo).``
|
|
789
|
+
> ``package-build config: `site.<key>` is not a recognized option (expected one of: base, assets, description, packages, sections, readmeSections, landing, trees, pass, passOptions, backfillSections, list, notfound, hugo).``
|
|
789
790
|
|
|
790
791
|
`site.assets` is the host every package's imagery is served from, and it is
|
|
791
792
|
the one address in this file that is not this repository's own. A note names
|
|
@@ -798,11 +799,30 @@ Absolute, and the trailing slash is trimmed:
|
|
|
798
799
|
|
|
799
800
|
> ``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
801
|
|
|
802
|
+
`content-build site` refuses to generate a configuration with no
|
|
803
|
+
`site.assets` at all — there is no defensible default, because the theme
|
|
804
|
+
resolves every relative asset against it:
|
|
805
|
+
|
|
806
|
+
> ``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.``
|
|
807
|
+
|
|
801
808
|
The generated Hugo configuration carries the same host as
|
|
802
809
|
`params.cdnBaseURL`, which the theme resolves a relative asset path against.
|
|
803
810
|
The two are one value read by two readers: the toolchain emits it into a
|
|
804
811
|
page, and the theme joins it onto anything the toolchain left relative.
|
|
805
812
|
|
|
813
|
+
`site.description` is the site's `<meta name="description">` — one plain
|
|
814
|
+
sentence, distinct from the Foundry package browser's pitch
|
|
815
|
+
([`packageBuild.manifest.descriptionHtml`](#packagebuildmanifest), which
|
|
816
|
+
allows HTML). Required for `content-build site`, the way `packageBuild.manifest.title`
|
|
817
|
+
is:
|
|
818
|
+
|
|
819
|
+
> ``package-build config: `site.description` is not declared, and the site's `<meta name="description">` reads from it.``
|
|
820
|
+
|
|
821
|
+
Markup belongs in `descriptionHtml`, not here — a value containing `<` is
|
|
822
|
+
refused:
|
|
823
|
+
|
|
824
|
+
> ``package-build config: `site.description` contains `<` — this is plain text for the site's `<meta name="description">`; markup belongs in `packageBuild.manifest.descriptionHtml`.``
|
|
825
|
+
|
|
806
826
|
`site.packages` names which content packages' notes the site walks, beyond
|
|
807
827
|
this one's own; `site.pass` names a repository's own body-rewrite bundle
|
|
808
828
|
(the one part of the site contract that is code, exactly as `itemBuilders`
|
|
@@ -956,24 +976,26 @@ the block cannot grow into a second configuration file:
|
|
|
956
976
|
`content-build site` writes `build/hugo/hugo.toml` on every run. Every value
|
|
957
977
|
in it has one source, and that source is where it is edited:
|
|
958
978
|
|
|
959
|
-
| Key | Derived from
|
|
960
|
-
| --------------------------------- |
|
|
961
|
-
| `baseURL` | `package.json` `homepage`, checked by `checkHomepage` — an absolute URL ending `/<contentPackage>/`
|
|
962
|
-
| `title` | `packageBuild.manifest.title`, which is required
|
|
963
|
-
| `locale` | the organisation's locale, `en-us`, in `engine/site-config.mjs`
|
|
964
|
-
| `publishDir` | `contentPackage`, under the deployment root `build/site` — written relative to `build/hugo/`, so `../site/<contentPackage>`
|
|
965
|
-
| `contentDir` | the fixed content mount, `build/hugo/content` — written as `content`
|
|
966
|
-
| `themesDir` | where `@heroiclands/hugo-theme` is installed, resolved the way Node resolves a package and written relative to `build/hugo/`
|
|
967
|
-
| `theme` | the installed `@heroiclands/hugo-theme`, so `hugo-theme`
|
|
968
|
-
| `disableKinds` |
|
|
969
|
-
| `
|
|
970
|
-
| `
|
|
971
|
-
| `params.
|
|
972
|
-
| `params.
|
|
973
|
-
| `params.
|
|
974
|
-
| `params.
|
|
975
|
-
| `
|
|
976
|
-
| `
|
|
979
|
+
| Key | Derived from |
|
|
980
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
981
|
+
| `baseURL` | `package.json` `homepage`, checked by `checkHomepage` — an absolute URL ending `/<contentPackage>/` |
|
|
982
|
+
| `title` | `packageBuild.manifest.title`, which is required |
|
|
983
|
+
| `locale` | the organisation's locale, `en-us`, in `engine/site-config.mjs` |
|
|
984
|
+
| `publishDir` | `contentPackage`, under the deployment root `build/site` — written relative to `build/hugo/`, so `../site/<contentPackage>` |
|
|
985
|
+
| `contentDir` | the fixed content mount, `build/hugo/content` — written as `content` |
|
|
986
|
+
| `themesDir` | where `@heroiclands/hugo-theme` is installed, resolved the way Node resolves a package and written relative to `build/hugo/` |
|
|
987
|
+
| `theme` | the installed `@heroiclands/hugo-theme`, so `hugo-theme` |
|
|
988
|
+
| `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 |
|
|
989
|
+
| `taxonomies` | the same fact — written as `{ tag = "tags" }` when at least one note carries `tags:`, absent otherwise |
|
|
990
|
+
| `outputs` | the same fact — written as `{ taxonomy = ["HTML"], term = ["HTML"] }` when at least one note carries `tags:`, absent otherwise |
|
|
991
|
+
| `params.description` | `site.description`, which is required |
|
|
992
|
+
| `params.author` | `package.json` `author`, its `name`; absent when the package declares none |
|
|
993
|
+
| `params.cdnBaseURL` | `site.assets`, which is required |
|
|
994
|
+
| `params.brand` | the organisation's brand links — `logo`, `licenseURL`, `discordURL` — in `engine/site-config.mjs` |
|
|
995
|
+
| `params.list` | `site.list` |
|
|
996
|
+
| `params.notfound` | `site.notfound`; absent when undeclared |
|
|
997
|
+
| `markup.goldmark.renderer.unsafe` | the toolchain, whose pages carry raw HTML — a `<figure>` for every image, a `<span>` marking an unresolved link |
|
|
998
|
+
| `menu` | the navigation `content-build deps fetch` caches from `https://www.heroiclands.org/nav.json`, entry for entry, a dropdown's entries as `parent` entries |
|
|
977
999
|
|
|
978
1000
|
The site build reads the navigation from the cache only. A cold cache is an
|
|
979
1001
|
error naming the command that fills it:
|
|
@@ -989,9 +1011,24 @@ none fails the site build:
|
|
|
989
1011
|
|
|
990
1012
|
> ``package-build config: `packageBuild.manifest.title` is not declared, and the site's `title` reads from it.``
|
|
991
1013
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1014
|
+
`params.description` reads from `site.description` the same way, and fails
|
|
1015
|
+
the same way when it is absent:
|
|
1016
|
+
|
|
1017
|
+
> ``package-build config: `site.description` is not declared, and the site's `<meta name="description">` reads from it.``
|
|
1018
|
+
|
|
1019
|
+
`params.cdnBaseURL` reads from `site.assets`, and the theme resolves every
|
|
1020
|
+
relative asset against it, so a configuration declaring none fails the site
|
|
1021
|
+
build the same way:
|
|
1022
|
+
|
|
1023
|
+
> ``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.``
|
|
1024
|
+
|
|
1025
|
+
Nothing else is emitted. A site whose notes carry no `tags:` publishes no
|
|
1026
|
+
taxonomy pages — `[taxonomies]` and `[outputs]` go unwritten, and Hugo's
|
|
1027
|
+
defaults never apply because `taxonomy` and `term` are disabled kinds. A site
|
|
1028
|
+
with at least one tagged note publishes `/tags/` and a page per tag: Hugo's
|
|
1029
|
+
own default taxonomy pair also declares `category`, so `[taxonomies]` names
|
|
1030
|
+
only `tag`, and `[outputs]` restricts both to `HTML` so neither produces a
|
|
1031
|
+
feed. Every other key is `site.hugo`'s to add.
|
|
995
1032
|
|
|
996
1033
|
### `pdf`
|
|
997
1034
|
|
|
@@ -1149,6 +1186,7 @@ Each entry, in any of the four lists:
|
|
|
1149
1186
|
| `relationships.systems[].manifest` | string | no | none |
|
|
1150
1187
|
| `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
|
|
1151
1188
|
| `relationships.systems[].itemCatalog` | boolean | no | `false` |
|
|
1189
|
+
| `relationships.systems[].contentIndex` | boolean | no | `true` |
|
|
1152
1190
|
|
|
1153
1191
|
(the same keys apply under `requires[]`, `recommends[]` and
|
|
1154
1192
|
`conflicts[]`.)
|
|
@@ -1157,7 +1195,7 @@ Each entry, in any of the four lists:
|
|
|
1157
1195
|
|
|
1158
1196
|
> ``package-build config: `relationships.<kind>[<index>].id` must be a non-empty string.``
|
|
1159
1197
|
|
|
1160
|
-
> ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog).``
|
|
1198
|
+
> ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog, contentIndex).``
|
|
1161
1199
|
|
|
1162
1200
|
`contentPackage` names what the other package's _content_ is called, where
|
|
1163
1201
|
that differs from its Foundry id. A note addresses a file by the content
|
|
@@ -1176,6 +1214,20 @@ item catalogue at build time. It requires a `manifest`:
|
|
|
1176
1214
|
|
|
1177
1215
|
> ``package-build config: `relationships.<kind>[<index>].itemCatalog` needs a `manifest` naming the package to fetch.``
|
|
1178
1216
|
|
|
1217
|
+
`contentIndex` and `itemCatalog` are the two edges a relationship may declare,
|
|
1218
|
+
and a package may have either without the other. `itemCatalog` says a
|
|
1219
|
+
dependency supplies _items_; `contentIndex`, `true` by default, says
|
|
1220
|
+
`deps fetch` fetches its published note index and this tree may cite its
|
|
1221
|
+
addresses by wikilink. Declaring `contentIndex: false` narrows the
|
|
1222
|
+
relationship to the Foundry manifest only — a dependency Foundry installs but
|
|
1223
|
+
this tree never cites — so `deps fetch` fetches nothing for it and a wikilink
|
|
1224
|
+
into it fails, naming the key, rather than resolving against a stale
|
|
1225
|
+
declaration or an index nobody fetched:
|
|
1226
|
+
|
|
1227
|
+
> ``package-build config: `relationships.<kind>[<index>].contentIndex` must be true or false.``
|
|
1228
|
+
|
|
1229
|
+
> ``package-build config: `relationships.<kind>[<index>].contentIndex` cannot be false together with `itemCatalog: true` — a catalogue is fetched from the same index.``
|
|
1230
|
+
|
|
1179
1231
|
### `systems`
|
|
1180
1232
|
|
|
1181
1233
|
**Type:** object (`{id: spec}`) · **Optional** · default `{}`. Refused in a
|
|
@@ -1415,11 +1467,22 @@ that has a wrong answer rather than an unknown one: a key the build
|
|
|
1415
1467
|
silently overwritten and the two would be free to disagree with nothing to
|
|
1416
1468
|
say so.
|
|
1417
1469
|
|
|
1470
|
+
`packageBuild.manifest.descriptionHtml` is the exception worth calling out on
|
|
1471
|
+
its own: it is not forbidden, it **is** how `description` is authored. It is
|
|
1472
|
+
the pitch Foundry's package browser shows — HTML allowed, any length — and it
|
|
1473
|
+
is emitted into the generated manifest as `description`; the key itself never
|
|
1474
|
+
survives into the manifest under its own name. `package.json`'s own
|
|
1475
|
+
`description` is read by neither this nor the site (see
|
|
1476
|
+
[`site.description`](#site)) — a declared one is reported as a warning naming
|
|
1477
|
+
both real keys, so it cannot drift back into use:
|
|
1478
|
+
|
|
1479
|
+
> `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.
|
|
1480
|
+
|
|
1418
1481
|
| Forbidden key | Derived from |
|
|
1419
1482
|
| ------------------------------------- | ---------------------------------------------------------------- |
|
|
1420
1483
|
| `packageBuild.manifest.id` | `foundryPackage`, itself derived from `package.json` `name` |
|
|
1421
1484
|
| `packageBuild.manifest.version` | `package.json` `version` |
|
|
1422
|
-
| `packageBuild.manifest.description` | `
|
|
1485
|
+
| `packageBuild.manifest.description` | `packageBuild.manifest.descriptionHtml` |
|
|
1423
1486
|
| `packageBuild.manifest.url` | `package.json` `repository` |
|
|
1424
1487
|
| `packageBuild.manifest.bugs` | `package.json` `repository` |
|
|
1425
1488
|
| `packageBuild.manifest.manifest` | `package.json` `repository` and the release tag |
|
|
@@ -1430,7 +1493,7 @@ say so.
|
|
|
1430
1493
|
|
|
1431
1494
|
> ``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.``
|
|
1432
1495
|
|
|
1433
|
-
> ``package-build config: `packageBuild.manifest.description` is derived from
|
|
1496
|
+
> ``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.``
|
|
1434
1497
|
|
|
1435
1498
|
> ``package-build config: `packageBuild.manifest` must be a mapping.``
|
|
1436
1499
|
|
|
@@ -1647,17 +1710,17 @@ the source directory:
|
|
|
1647
1710
|
|
|
1648
1711
|
## Every retired or forbidden key, in one place
|
|
1649
1712
|
|
|
1650
|
-
| Key
|
|
1651
|
-
|
|
|
1652
|
-
| `publish.address.landing`
|
|
1653
|
-
| `packs[].folders`
|
|
1654
|
-
| `rootDir`
|
|
1655
|
-
| `foundryPackage`
|
|
1656
|
-
| `homepage`, `author`
|
|
1657
|
-
| `stats.systemId`
|
|
1658
|
-
| `stats.systemVersion`
|
|
1659
|
-
| `packageBuild.manifest.id`, `.version`, `.description`, `.url`, `.bugs`, `.manifest`, `.download`, `.compatibility`, `.relationships`, `.packs`
|
|
1660
|
-
| `site.out`
|
|
1661
|
-
| `site.hugo.baseURL`, `.title`, `.locale`, `.publishDir`, `.contentDir`, `.themesDir`, `.theme`, `.disableKinds`, `.params.description`, `.params.author`, `.params.cdnBaseURL`, `.params.brand`, `.params.list`, `.params.notfound`, `.markup.goldmark.renderer.unsafe`, `.menu` | Forbidden — each is written by the site build from a source it names; see [the generated Hugo configuration](#the-generated-hugo-configuration). |
|
|
1662
|
-
| `publish.site: true` / `publish.site: false`
|
|
1663
|
-
| `packs`, `itemBuilders`, `docs`, `compatibility`, `relationships`, `systems`, `requiresSystem`, `stats`, `foundryPackage`
|
|
1713
|
+
| Key | Why |
|
|
1714
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1715
|
+
| `publish.address.landing` | Retired — named a whole-section landing, and there are no sections to address. |
|
|
1716
|
+
| `packs[].folders` | Retired — a folder is a note (`type: folder`), materialised by the pack whose documents reference it. |
|
|
1717
|
+
| `rootDir` | Forbidden in a YAML configuration — always the file's own directory. |
|
|
1718
|
+
| `foundryPackage` | Forbidden in a YAML configuration — always the adjacent `package.json` `name`. |
|
|
1719
|
+
| `homepage`, `author` | Forbidden in a YAML configuration — always the adjacent `package.json`'s own `homepage` and `author`. |
|
|
1720
|
+
| `stats.systemId` | Forbidden in every configuration — derived from `packageKind`, `requiresSystem` or a lone declared system. |
|
|
1721
|
+
| `stats.systemVersion` | Forbidden in every configuration — derived from `package.json` (a system) or `systems:` / `relationships.systems` (a module). |
|
|
1722
|
+
| `packageBuild.manifest.id`, `.version`, `.description`, `.url`, `.bugs`, `.manifest`, `.download`, `.compatibility`, `.relationships`, `.packs` | Forbidden — each is derived from `package.json` or the top level of `package-build.config.yaml`; see [`packageBuild.manifest`](#packagebuildmanifest). |
|
|
1723
|
+
| `site.out` | Retired — the site build writes its content mount at `build/hugo/content`, beside the generated `hugo.toml`. |
|
|
1724
|
+
| `site.hugo.baseURL`, `.title`, `.locale`, `.publishDir`, `.contentDir`, `.themesDir`, `.theme`, `.disableKinds`, `.taxonomies`, `.outputs`, `.params.description`, `.params.author`, `.params.cdnBaseURL`, `.params.brand`, `.params.list`, `.params.notfound`, `.markup.goldmark.renderer.unsafe`, `.menu` | Forbidden — each is written by the site build from a source it names; see [the generated Hugo configuration](#the-generated-hugo-configuration). |
|
|
1725
|
+
| `publish.site: true` / `publish.site: false` | Refused rather than mapped — write `homepage` or `content`. |
|
|
1726
|
+
| `packs`, `itemBuilders`, `docs`, `compatibility`, `relationships`, `systems`, `requiresSystem`, `stats`, `foundryPackage` | Forbidden in a `documentation` package — each describes a Foundry package this kind is not; see the key's own section for its located refusal message. |
|
package/engine/content-links.mjs
CHANGED
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
PACKAGE_BASE,
|
|
87
87
|
readCanonicalKey,
|
|
88
88
|
} from "./content-address.mjs";
|
|
89
|
-
import { loadForeignIndexes } from "./metadata-index.mjs";
|
|
89
|
+
import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
90
90
|
import { frontmatterWikilinks, slugify } from "./web-wikilinks.mjs";
|
|
91
91
|
import { homepageAddresses, isHomepage } from "./homepage.mjs";
|
|
92
92
|
import { RETIRED_TYPES } from "./ids.mjs";
|
|
@@ -268,6 +268,10 @@ export function buildLinkIndex(
|
|
|
268
268
|
for (const v of foreign.index.values()) if (v.type) types.add(v.type);
|
|
269
269
|
|
|
270
270
|
const packages = new Set([...(byKey.size ? [pkg] : []), ...foreign.packages]);
|
|
271
|
+
// Packages declared `contentIndex: false` — a Foundry dependency only, with
|
|
272
|
+
// no fetched index. A link naming one is refused with a diagnostic that
|
|
273
|
+
// names the key, rather than reading as an undeclared package or a typo.
|
|
274
|
+
const noIndexPackages = config ? noContentIndexPackages(config) : new Set();
|
|
271
275
|
|
|
272
276
|
// The address space an `![[…]]` embed resolves against, shaped as every
|
|
273
277
|
// other asset resolver reads one so the checker cannot answer an authored
|
|
@@ -464,7 +468,7 @@ export function buildLinkIndex(
|
|
|
464
468
|
* @returns {object|undefined} The note it addresses.
|
|
465
469
|
*/
|
|
466
470
|
function resolveAddress(target, keyPath) {
|
|
467
|
-
const qualified = readQualifier(target, types, packages);
|
|
471
|
+
const qualified = readQualifier(target, types, packages, noIndexPackages);
|
|
468
472
|
if (!qualified || qualified.reason) return undefined;
|
|
469
473
|
// Every omitted segment defaults from where the link is written,
|
|
470
474
|
// so the target expands to exactly one canonical address and this is a
|
|
@@ -501,7 +505,7 @@ export function buildLinkIndex(
|
|
|
501
505
|
* @returns {object[]} The foreign entries, each carrying its `package`.
|
|
502
506
|
*/
|
|
503
507
|
function foreignHits(target, keyPath) {
|
|
504
|
-
const q = readQualifier(target, types, packages);
|
|
508
|
+
const q = readQualifier(target, types, packages, noIndexPackages);
|
|
505
509
|
if (!q || q.reason) return [];
|
|
506
510
|
// An omitted package means *this* package, so a short form
|
|
507
511
|
// addresses nothing foreign and never reaches a dependency's index.
|
|
@@ -552,7 +556,7 @@ export function buildLinkIndex(
|
|
|
552
556
|
* @returns {object|null} The note, asset record or foreign entry declaring it.
|
|
553
557
|
*/
|
|
554
558
|
function referenceHit(target) {
|
|
555
|
-
const q = readQualifier(target, types, packages);
|
|
559
|
+
const q = readQualifier(target, types, packages, noIndexPackages);
|
|
556
560
|
if (!q || q.reason) return null;
|
|
557
561
|
const local = matchAddress([...byKey, ...byAssetKey], q);
|
|
558
562
|
if (local.length) return local[0][1];
|
|
@@ -566,6 +570,8 @@ export function buildLinkIndex(
|
|
|
566
570
|
anchors,
|
|
567
571
|
types,
|
|
568
572
|
packages,
|
|
573
|
+
/** Packages declared `contentIndex: false`, a Foundry dependency only. */
|
|
574
|
+
noIndexPackages,
|
|
569
575
|
/**
|
|
570
576
|
* The files this package ships, by canonical address. Separate from the
|
|
571
577
|
* notes because the two record shapes are read differently, and exposed
|
|
@@ -597,7 +603,7 @@ export function buildLinkIndex(
|
|
|
597
603
|
foreignHits,
|
|
598
604
|
referenceHit,
|
|
599
605
|
/** Whether a target reads as a qualified address at all. */
|
|
600
|
-
isAddress: (target) => Boolean(readQualifier(target, types, packages)),
|
|
606
|
+
isAddress: (target) => Boolean(readQualifier(target, types, packages, noIndexPackages)),
|
|
601
607
|
};
|
|
602
608
|
}
|
|
603
609
|
|
|
@@ -922,8 +928,9 @@ export function auditHomepageLinks(index) {
|
|
|
922
928
|
* which addresses a foreign manifest answered. Each `deadAddresses` entry
|
|
923
929
|
* carries a `reason` from {@link LINK_FINDING_REASONS} —
|
|
924
930
|
* `"not-an-address"`, `"unknown-type"`, `"ambiguous"` (with the claiming
|
|
925
|
-
* `packages`), or `"unresolved"` — and every one of
|
|
926
|
-
* the three resolvers agree on severity for every
|
|
931
|
+
* `packages`), `"no-content-index"`, or `"unresolved"` — and every one of
|
|
932
|
+
* them is an **error**: the three resolvers agree on severity for every
|
|
933
|
+
* class.
|
|
927
934
|
*/
|
|
928
935
|
export function auditLinks(index) {
|
|
929
936
|
const { notes, anchors, linksOf, embedsOf, resolve, manifestHit, isAddress } = index;
|
|
@@ -1004,10 +1011,13 @@ export function auditLinks(index) {
|
|
|
1004
1011
|
});
|
|
1005
1012
|
continue;
|
|
1006
1013
|
}
|
|
1007
|
-
const read = readQualifier(target, index.types, index.packages);
|
|
1014
|
+
const read = readQualifier(target, index.types, index.packages, index.noIndexPackages);
|
|
1008
1015
|
deadAddresses.push({
|
|
1009
1016
|
...at,
|
|
1010
|
-
reason:
|
|
1017
|
+
reason:
|
|
1018
|
+
read?.reason === "unknown-type" ? "unknown-type"
|
|
1019
|
+
: read?.reason === "no-content-index" ? "no-content-index"
|
|
1020
|
+
: "unresolved",
|
|
1011
1021
|
});
|
|
1012
1022
|
}
|
|
1013
1023
|
}
|
package/engine/helpers.mjs
CHANGED
|
@@ -43,7 +43,7 @@ import { contentPackage, foundryPackageId } from "./content-package.mjs";
|
|
|
43
43
|
import { searchableFrontmatter } from "./note-package.mjs";
|
|
44
44
|
import { PACKAGE_BASE } from "./content-address.mjs";
|
|
45
45
|
import { resolveNoteId } from "./note-ids.mjs";
|
|
46
|
-
import { loadForeignIndexes } from "./metadata-index.mjs";
|
|
46
|
+
import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
47
47
|
// The record accessors only — deriving records reaches the pack router and the
|
|
48
48
|
// manifest emitter, which reach the compilers, which load this module. Reading
|
|
49
49
|
// a record needs none of that.
|
|
@@ -787,6 +787,7 @@ export function buildContentLinkIndex(
|
|
|
787
787
|
);
|
|
788
788
|
return buildWikilinkIndex(docs, resolved.foundryPackage, foreign, resolved.contentPackage, {
|
|
789
789
|
assets,
|
|
790
|
+
noIndexPackages: noContentIndexPackages(resolved),
|
|
790
791
|
});
|
|
791
792
|
}
|
|
792
793
|
|
|
@@ -84,6 +84,11 @@ export const METADATA_RELATIONSHIP_KINDS = Object.freeze(["systems", "requires"]
|
|
|
84
84
|
* and needing no items is the mirror of it. Gating the index on the catalogue
|
|
85
85
|
* flag would serve neither.
|
|
86
86
|
*
|
|
87
|
+
* **Excludes a relationship declaring `contentIndex: false`.** That opts a
|
|
88
|
+
* dependency out of both edges at once: it is a Foundry dependency only, cited
|
|
89
|
+
* by neither a wikilink nor an item reference, so there is nothing here for
|
|
90
|
+
* `deps fetch` to fill and no cache this build will ever read.
|
|
91
|
+
*
|
|
87
92
|
* The declaration is the one already in the emitted `system.json` /
|
|
88
93
|
* `module.json`, so it cannot drift from what Foundry itself installs, and
|
|
89
94
|
* there is no new configuration key to keep in step. Each entry carries the
|
|
@@ -98,6 +103,7 @@ export function metadataRelationships(config) {
|
|
|
98
103
|
const out = [];
|
|
99
104
|
for (const kind of METADATA_RELATIONSHIP_KINDS) {
|
|
100
105
|
for (const rel of config?.relationships?.[kind] ?? []) {
|
|
106
|
+
if (rel.contentIndex === false) continue;
|
|
101
107
|
out.push({
|
|
102
108
|
id: rel.id,
|
|
103
109
|
manifest: rel.manifest,
|
|
@@ -109,6 +115,32 @@ export function metadataRelationships(config) {
|
|
|
109
115
|
return out;
|
|
110
116
|
}
|
|
111
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Every package a relationship declares `contentIndex: false` on, keyed by
|
|
120
|
+
* the content package name a link into it would use.
|
|
121
|
+
*
|
|
122
|
+
* A separate set from {@link metadataRelationships}, which answers "what does
|
|
123
|
+
* `deps fetch` fill" — this answers "what does the link resolver recognise as
|
|
124
|
+
* a package with no fetched index", which a wikilink checker or pack compiler
|
|
125
|
+
* needs to tell that case apart from a package nobody declared at all.
|
|
126
|
+
*
|
|
127
|
+
* Walked across every relationship kind, not only the citable ones: the
|
|
128
|
+
* config validation refuses the flag nowhere by kind, so a resolver reading it
|
|
129
|
+
* back should not assume one either.
|
|
130
|
+
*
|
|
131
|
+
* @param {object} config - The resolved build configuration.
|
|
132
|
+
* @returns {ReadonlySet<string>} The content package names.
|
|
133
|
+
*/
|
|
134
|
+
export function noContentIndexPackages(config) {
|
|
135
|
+
const out = new Set();
|
|
136
|
+
for (const entries of Object.values(config?.relationships ?? {})) {
|
|
137
|
+
for (const rel of entries ?? []) {
|
|
138
|
+
if (rel.contentIndex === false) out.add(rel.contentPackage ?? rel.id);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Object.freeze(out);
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
/**
|
|
113
145
|
* The cache directory for one dependency's index at one version.
|
|
114
146
|
*
|
|
@@ -496,6 +496,26 @@ export function hasTag(fm, tag) {
|
|
|
496
496
|
return false;
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Whether a note carries any `tags:` at all, however authored.
|
|
501
|
+
*
|
|
502
|
+
* The one question the site build asks of tags in aggregate — whether the
|
|
503
|
+
* tree publishes taxonomy pages — rather than about a particular tag. Reads
|
|
504
|
+
* `tags` and `tag` exactly as {@link hasTag} does, and treats an empty list
|
|
505
|
+
* or a blank string as carrying none.
|
|
506
|
+
*
|
|
507
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
508
|
+
* @returns {boolean} Whether the note carries at least one tag.
|
|
509
|
+
*/
|
|
510
|
+
export function hasAnyTag(fm) {
|
|
511
|
+
const raw = fm?.tags ?? fm?.tag;
|
|
512
|
+
if (raw == null) return false;
|
|
513
|
+
for (const entry of Array.isArray(raw) ? raw : [raw]) {
|
|
514
|
+
if (typeof entry === "string" && entry.trim() !== "") return true;
|
|
515
|
+
}
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
|
|
499
519
|
/**
|
|
500
520
|
* Whether a note is tagged as an unfinished **draft**.
|
|
501
521
|
*
|
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/schema-check.mjs
CHANGED
|
@@ -466,8 +466,8 @@ export function compareEmittedSystem({
|
|
|
466
466
|
* Two places to find it, because a system checks itself against source it owns
|
|
467
467
|
* while a module checks against a dependency it fetched:
|
|
468
468
|
*
|
|
469
|
-
* - **A system**: its own `schema.json`, generated from its `src/`
|
|
470
|
-
*
|
|
469
|
+
* - **A system**: its own `schema.json`, generated from its `src/` into
|
|
470
|
+
* `build/` by `package-build schema`.
|
|
471
471
|
* - **A module**: the copy cached by `content-build deps fetch`, from the
|
|
472
472
|
* archive of the version it pins — which is what makes the comparison happen
|
|
473
473
|
* at `verified` rather than against whatever the system's `main` holds today.
|
|
@@ -505,7 +505,7 @@ export function resolveSchemaArtifact(config, system = undefined) {
|
|
|
505
505
|
|
|
506
506
|
// The system checking itself, against the schema its own build published.
|
|
507
507
|
if (config.packageKind === "systems" && config.foundryPackage === systemId) {
|
|
508
|
-
const own = path.join(config.rootDir, SCHEMA_ARTIFACT_FILE);
|
|
508
|
+
const own = path.join(config.rootDir, "build", SCHEMA_ARTIFACT_FILE);
|
|
509
509
|
return fs.existsSync(own) ? read(own) : null;
|
|
510
510
|
}
|
|
511
511
|
|
|
@@ -683,9 +683,9 @@ export function checkAuthoredSystemData(
|
|
|
683
683
|
* subtype declares.
|
|
684
684
|
*
|
|
685
685
|
* The build-time face of {@link compareEmittedSystem}: it resolves the schema
|
|
686
|
-
* the way every other check here does — the system's own
|
|
687
|
-
* the cached one from the release a module pins — and attaches the message
|
|
688
|
-
* reader sees.
|
|
686
|
+
* the way every other check here does — the system's own published artifact,
|
|
687
|
+
* or the cached one from the release a module pins — and attaches the message
|
|
688
|
+
* a reader sees.
|
|
689
689
|
*
|
|
690
690
|
* **Silent where there is nothing to check against**, exactly as its two
|
|
691
691
|
* siblings are: a module pinning a system version released before the artifact
|
package/engine/site-build.mjs
CHANGED
|
@@ -57,13 +57,14 @@ import { renderImageFigures } from "./content-images.mjs";
|
|
|
57
57
|
import { pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
58
58
|
import { buildSiteIndex, resolveInfoboxRef, wikiContext } from "./site-index.mjs";
|
|
59
59
|
import { frontmatterWikilinks, resolveWebWikilinks } from "./web-wikilinks.mjs";
|
|
60
|
-
import { loadForeignIndexes } from "./metadata-index.mjs";
|
|
60
|
+
import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
61
61
|
import { noteInfoboxes } from "./infobox-registry.mjs";
|
|
62
62
|
import { formatUnaddressableFinding, unaddressableForeignPackages } from "./metadata-index.mjs";
|
|
63
63
|
import { deriveBeingInfo, isBeing } from "../sohl/being-info.mjs";
|
|
64
64
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
65
65
|
import { routerFor } from "./pack-router.mjs";
|
|
66
66
|
import { searchableFrontmatter } from "./note-package.mjs";
|
|
67
|
+
import { hasAnyTag } from "./note-vocabulary.mjs";
|
|
67
68
|
// The corpus, from the one pass that derives it.
|
|
68
69
|
import { indexRecordsFor } from "./content-index.mjs";
|
|
69
70
|
import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
@@ -476,7 +477,10 @@ export function siteGates(pages, findings, { config }) {
|
|
|
476
477
|
out.unaddressable = unaddressableForeignPackages(foreign.index);
|
|
477
478
|
if (out.unaddressable.length) return out;
|
|
478
479
|
|
|
479
|
-
const index = buildSiteIndex(pages, {
|
|
480
|
+
const index = buildSiteIndex(pages, {
|
|
481
|
+
foreignIndex: foreign.index,
|
|
482
|
+
noIndexPackages: noContentIndexPackages(config),
|
|
483
|
+
});
|
|
480
484
|
out.conflicts = index.conflicts;
|
|
481
485
|
if (out.conflicts.length) return out;
|
|
482
486
|
|
|
@@ -1043,7 +1047,10 @@ export function resolveSitePass(name, options) {
|
|
|
1043
1047
|
* `sql` directive with none prepared is a table error: nothing here runs a
|
|
1044
1048
|
* query.
|
|
1045
1049
|
* @returns {{gates: object, stats: object|null, tableErrors: object[],
|
|
1046
|
-
* wikiErrors: object[], imageErrors: object[], manifests: object|null
|
|
1050
|
+
* wikiErrors: object[], imageErrors: object[], manifests: object|null,
|
|
1051
|
+
* hasTags: boolean}} `hasTags` is whether any note the walk read carries
|
|
1052
|
+
* `tags:` — what {@link module:engine/site-config.hugoConfig} reads to
|
|
1053
|
+
* decide whether the site emits taxonomy pages.
|
|
1047
1054
|
*/
|
|
1048
1055
|
export function buildSite({ config, sqlTables } = {}) {
|
|
1049
1056
|
const resolved = config ?? loadPackConfig();
|
|
@@ -1142,6 +1149,7 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1142
1149
|
wikiErrors: [],
|
|
1143
1150
|
imageErrors: [],
|
|
1144
1151
|
stats: null,
|
|
1152
|
+
hasTags: homepages.some((p) => hasAnyTag(p.fm)),
|
|
1145
1153
|
};
|
|
1146
1154
|
}
|
|
1147
1155
|
|
|
@@ -1164,6 +1172,7 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1164
1172
|
landings: 0,
|
|
1165
1173
|
out: homeRoot,
|
|
1166
1174
|
},
|
|
1175
|
+
hasTags: homepages.some((p) => hasAnyTag(p.fm)),
|
|
1167
1176
|
};
|
|
1168
1177
|
}
|
|
1169
1178
|
|
|
@@ -1211,6 +1220,7 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1211
1220
|
tableErrors: [],
|
|
1212
1221
|
wikiErrors: [],
|
|
1213
1222
|
imageErrors: [],
|
|
1223
|
+
hasTags: [...pages, ...homepageEntries].some((p) => hasAnyTag(p.fm)),
|
|
1214
1224
|
};
|
|
1215
1225
|
}
|
|
1216
1226
|
|
|
@@ -1269,6 +1279,7 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1269
1279
|
landings,
|
|
1270
1280
|
out,
|
|
1271
1281
|
},
|
|
1282
|
+
hasTags: [...pages, ...homepageEntries].some((p) => hasAnyTag(p.fm)),
|
|
1272
1283
|
};
|
|
1273
1284
|
}
|
|
1274
1285
|
|