@heroiclands/package-build 22.1.0 → 22.1.1
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 +15 -0
- package/CONTENT.md +22 -0
- package/bin/content-build.mjs +29 -3
- package/content-config.mjs +30 -1
- package/docs/api.md +25 -23
- package/docs/configuration.md +57 -36
- 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/site-build.mjs +14 -3
- package/engine/site-config.mjs +47 -6
- 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/package.json +1 -1
- 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/site-build.d.mts +5 -1
- package/types/engine/site-config.d.mts +18 -5
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f02b25a: **A relationship may declare `contentIndex: false`.** A `requires` or
|
|
8
|
+
`systems` entry naming a package Foundry installs but the content tree never
|
|
9
|
+
cites by wikilink no longer needs a fetched index just to satisfy
|
|
10
|
+
`deps fetch`. `contentIndex` (default `true`) narrows a relationship declaring
|
|
11
|
+
it `false` to the Foundry manifest only: `deps fetch` fetches nothing for it,
|
|
12
|
+
and a wikilink into it fails at the link, naming the key, rather than
|
|
13
|
+
resolving against a stale declaration or an index nobody fetched. It cannot be
|
|
14
|
+
combined with `itemCatalog: true`, which extracts items from the same index
|
|
15
|
+
this declares there is none of.
|
|
16
|
+
- 07bf15d: **The generated Hugo configuration emits tag pages for a site whose notes carry `tags:`.** `content-build site` reads whether any note in the tree carries `tags:` and, when at least one does, writes `[taxonomies] tag = "tags"` and `[outputs] taxonomy = ["HTML"], term = ["HTML"]` into `build/hugo/hugo.toml`, and leaves `taxonomy` and `term` enabled among `disableKinds`. A site whose notes carry no `tags:` gets the same configuration as before — `taxonomy`, `term` and `RSS` all disabled, no `[taxonomies]` or `[outputs]` block. `site.hugo.disableKinds`, `.taxonomies` and `.outputs` stay refused under `site.hugo`, each naming this derivation as the source.
|
|
17
|
+
|
|
3
18
|
## 22.1.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/CONTENT.md
CHANGED
|
@@ -1782,6 +1782,28 @@ asked for — a build that downloads silently is not reproducible and fails
|
|
|
1782
1782
|
strangely offline. The cache is keyed by version, so changing the pinned version
|
|
1783
1783
|
is a miss rather than a silent overwrite.
|
|
1784
1784
|
|
|
1785
|
+
### A relationship may be a Foundry dependency only
|
|
1786
|
+
|
|
1787
|
+
`requires` and `systems` install with Foundry whether or not the tree cites
|
|
1788
|
+
them, and a package may need the one without the other — thalornaaltart
|
|
1789
|
+
`requires` Thalorna so Foundry installs the base module, and its one homepage
|
|
1790
|
+
note links nowhere. Declare `contentIndex: false` on that entry to say so:
|
|
1791
|
+
|
|
1792
|
+
```yaml
|
|
1793
|
+
relationships:
|
|
1794
|
+
requires:
|
|
1795
|
+
- id: thalorna
|
|
1796
|
+
type: module
|
|
1797
|
+
manifest: https://github.com/HeroicLands/thalorna/releases/latest/download/module.json
|
|
1798
|
+
contentIndex: false
|
|
1799
|
+
```
|
|
1800
|
+
|
|
1801
|
+
`deps fetch` fetches nothing for it — no cache directory, nothing to go stale —
|
|
1802
|
+
and a wikilink into it fails at the link, naming `contentIndex`, rather than
|
|
1803
|
+
resolving against a stale declaration or an index nobody fetched. It cannot be
|
|
1804
|
+
combined with `itemCatalog: true`, which extracts items from the same index
|
|
1805
|
+
this declares there is none of.
|
|
1806
|
+
|
|
1785
1807
|
### `packagebuild` needs no declaration
|
|
1786
1808
|
|
|
1787
1809
|
package-build ships a set of images of its own — section banners chiefly — and a
|
package/bin/content-build.mjs
CHANGED
|
@@ -1492,7 +1492,10 @@ function pdfCommand() {
|
|
|
1492
1492
|
* **The Hugo configuration is generated before anything is written.** Its
|
|
1493
1493
|
* sources — `package.json`'s `homepage`, the cached navigation, the installed
|
|
1494
1494
|
* theme — are each a way the build can fail, and failing before the output
|
|
1495
|
-
* tree is cleared leaves the last good site in place to be looked at.
|
|
1495
|
+
* tree is cleared leaves the last good site in place to be looked at. It is
|
|
1496
|
+
* generated again once the site walk completes, because whether the site
|
|
1497
|
+
* emits taxonomy pages is read from the walk — whether any note carries
|
|
1498
|
+
* `tags:` — and that is not known until then.
|
|
1496
1499
|
*
|
|
1497
1500
|
* **Each gate is reported and the run stops at the first that fires.** They are
|
|
1498
1501
|
* ordered so the report names the cause rather than its symptoms: an unusable
|
|
@@ -1510,7 +1513,14 @@ function siteCommand() {
|
|
|
1510
1513
|
handler: async () => {
|
|
1511
1514
|
try {
|
|
1512
1515
|
const config = loadPackConfig();
|
|
1513
|
-
|
|
1516
|
+
// Generated once before the walk, purely to fail fast on a
|
|
1517
|
+
// missing or mismatched source — `homepage`, the manifest
|
|
1518
|
+
// title, the cached navigation, the installed theme — while
|
|
1519
|
+
// the last good site is still in place to be looked at. Its
|
|
1520
|
+
// `disableKinds`/`taxonomies`/`outputs` are provisional: only
|
|
1521
|
+
// the site walk below knows whether any note carries `tags:`,
|
|
1522
|
+
// so the value actually written is regenerated after it runs.
|
|
1523
|
+
generateHugoConfig(config);
|
|
1514
1524
|
const result = buildSite({
|
|
1515
1525
|
config,
|
|
1516
1526
|
sqlTables: await prepareTreeSqlTables(config.paths.content, {
|
|
@@ -1628,6 +1638,7 @@ function siteCommand() {
|
|
|
1628
1638
|
`${s.tree ?? 0} tree page(s) + ${s.landings} ` +
|
|
1629
1639
|
`landing(s) to ${path.relative(process.cwd(), s.out)}`,
|
|
1630
1640
|
);
|
|
1641
|
+
const hugo = generateHugoConfig(config, { hasTags: result.hasTags });
|
|
1631
1642
|
const { file } = writeHugoConfig(config, hugo);
|
|
1632
1643
|
log.info(`wrote ${path.relative(process.cwd(), file)}`);
|
|
1633
1644
|
} catch (err) {
|
|
@@ -1860,7 +1871,22 @@ function depsCommand() {
|
|
|
1860
1871
|
if (indexes) log.info(`Fetched ${indexes} dependency content index(es).`);
|
|
1861
1872
|
const count = await fetchAllCatalogs(config);
|
|
1862
1873
|
if (count) log.info(`Fetched ${count} dependency catalogue(s).`);
|
|
1863
|
-
if (!indexes && !count)
|
|
1874
|
+
if (!indexes && !count) {
|
|
1875
|
+
// Distinguished from a package declaring nothing at all: a
|
|
1876
|
+
// relationship may still be declared, just narrowed to the
|
|
1877
|
+
// Foundry manifest by `contentIndex: false` — reporting
|
|
1878
|
+
// "no dependencies" there would read as though the
|
|
1879
|
+
// declaration itself had gone missing.
|
|
1880
|
+
const declared = Object.values(config.relationships ?? {}).some(
|
|
1881
|
+
(entries) => entries?.length,
|
|
1882
|
+
);
|
|
1883
|
+
log.info(
|
|
1884
|
+
declared ?
|
|
1885
|
+
"No declared dependency needs a fetched content index or item " +
|
|
1886
|
+
"catalogue."
|
|
1887
|
+
: "This package declares no dependencies.",
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1864
1890
|
} catch (err) {
|
|
1865
1891
|
reportFailure(err);
|
|
1866
1892
|
process.exitCode = 1;
|
package/content-config.mjs
CHANGED
|
@@ -503,6 +503,14 @@ export function publishesContentPages(config) {
|
|
|
503
503
|
* package this one targets — for a system
|
|
504
504
|
* relationship, `verified` is what
|
|
505
505
|
* `_stats.systemVersion` is stamped from.
|
|
506
|
+
* @property {boolean} [contentIndex] Whether `deps fetch` fetches this
|
|
507
|
+
* dependency's content index. Default
|
|
508
|
+
* `true`. `false` declares the dependency
|
|
509
|
+
* for the Foundry manifest only — nothing
|
|
510
|
+
* this tree cites by wikilink — and refuses
|
|
511
|
+
* `itemCatalog: true` on the same entry,
|
|
512
|
+
* since a catalogue is fetched from the same
|
|
513
|
+
* index.
|
|
506
514
|
*/
|
|
507
515
|
|
|
508
516
|
/**
|
|
@@ -780,6 +788,7 @@ const RELATIONSHIP_KEYS = [
|
|
|
780
788
|
"manifest",
|
|
781
789
|
"compatibility",
|
|
782
790
|
"itemCatalog",
|
|
791
|
+
"contentIndex",
|
|
783
792
|
];
|
|
784
793
|
const AUTHOR_KEYS = ["name", "email", "url"];
|
|
785
794
|
const ITEM_BUILDER_KEYS = ["system", "img", "fields"];
|
|
@@ -1604,7 +1613,9 @@ export const DERIVED_HUGO_KEYS = Object.freeze({
|
|
|
1604
1613
|
contentDir: "the fixed content mount, `build/hugo/content`",
|
|
1605
1614
|
themesDir: "where `@heroiclands/hugo-theme` is installed",
|
|
1606
1615
|
theme: "the installed `@heroiclands/hugo-theme`",
|
|
1607
|
-
disableKinds: "
|
|
1616
|
+
disableKinds: "whether any note in the tree carries `tags:`, which the site walk discovers",
|
|
1617
|
+
taxonomies: "whether any note in the tree carries `tags:`, which the site walk discovers",
|
|
1618
|
+
outputs: "whether any note in the tree carries `tags:`, which the site walk discovers",
|
|
1608
1619
|
"params.description": "package.json `description`",
|
|
1609
1620
|
"params.author": "package.json `author`",
|
|
1610
1621
|
"params.cdnBaseURL": "`site.assets`",
|
|
@@ -2147,6 +2158,24 @@ function normalizeRelationships(value) {
|
|
|
2147
2158
|
}
|
|
2148
2159
|
spec.itemCatalog = rel.itemCatalog;
|
|
2149
2160
|
}
|
|
2161
|
+
// Opt-out: declares the dependency for the Foundry manifest
|
|
2162
|
+
// only, so `deps fetch` fetches no content index for it and a
|
|
2163
|
+
// wikilink into it is refused rather than silently dead. A
|
|
2164
|
+
// catalogue is fetched from the same index, so it cannot be
|
|
2165
|
+
// declared alongside `itemCatalog: true`.
|
|
2166
|
+
if (rel.contentIndex !== undefined) {
|
|
2167
|
+
if (typeof rel.contentIndex !== "boolean") {
|
|
2168
|
+
fail(`${at}.contentIndex`, "must be true or false");
|
|
2169
|
+
}
|
|
2170
|
+
if (rel.contentIndex === false && spec.itemCatalog) {
|
|
2171
|
+
fail(
|
|
2172
|
+
`${at}.contentIndex`,
|
|
2173
|
+
"cannot be false together with `itemCatalog: true` — a catalogue is " +
|
|
2174
|
+
"fetched from the same index",
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
spec.contentIndex = rel.contentIndex;
|
|
2178
|
+
}
|
|
2150
2179
|
return Object.freeze(spec);
|
|
2151
2180
|
}),
|
|
2152
2181
|
);
|
package/docs/api.md
CHANGED
|
@@ -342,6 +342,7 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
|
|
|
342
342
|
| `applicableTagGroups` | `applicableTagGroups(type, groups)` | `object[]` | reading the declared tag groups that apply to a note type |
|
|
343
343
|
| `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading the single-valued tag slots a note type has, such as a being's kind |
|
|
344
344
|
| `hasTag` | `hasTag(fm, tag)` | `boolean` | checking whether a note carries a given tag, whatever scalar-or-list form it was authored in |
|
|
345
|
+
| `hasAnyTag` | `hasAnyTag(fm)` | `boolean` | checking whether a note carries any `tags:` at all, for the site build's taxonomy decision |
|
|
345
346
|
| `isDraftNote` | `isDraftNote(fm)` | `boolean` | checking whether a note is tagged as an unfinished draft |
|
|
346
347
|
| `subTypeCharsetMessage` | `subTypeCharsetMessage(value)` | `string` | building the message for a `subType` outside the address charset |
|
|
347
348
|
| `typeCharsetMessage` | `typeCharsetMessage(type)` | `string` | building the message for a `type` outside the address charset |
|
|
@@ -491,21 +492,22 @@ The shortcodes a note declares it used to be published under. A package's `(type
|
|
|
491
492
|
|
|
492
493
|
The published content index — the artifact packages exchange addresses through. **A package publishes its own index; a consumer fetches the ones it depends on.** That is the whole mechanism, and it replaces a vendored link manifest that each repository committed a copy of every other repository's file into. Vendoring failed three ways, and only the last is about staleness:
|
|
493
494
|
|
|
494
|
-
| Export | Signature | Returns | Use it when
|
|
495
|
-
| ------------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
496
|
-
| `METADATA_RELATIONSHIP_KINDS` | `const METADATA_RELATIONSHIP_KINDS` | — | The relationship kinds that are dependencies, and therefore citable.
|
|
497
|
-
| `metadataFileName` | `function metadataFileName(pkg)` | {string} The file name, e.g. | What a package's content index is called, wherever it is written or fetched.
|
|
498
|
-
| `metadataRelationships` | `function metadataRelationships(config)` | {Array<{id: string, manifest: string, kind: string, verified: string\|undefined}>} The dependencies, in declaration order. | Every dependency whose published index this build resolves addresses through.
|
|
499
|
-
| `
|
|
500
|
-
| `
|
|
501
|
-
| `
|
|
502
|
-
| `
|
|
503
|
-
| `
|
|
504
|
-
| `
|
|
505
|
-
| `
|
|
506
|
-
| `
|
|
507
|
-
| `
|
|
508
|
-
| `
|
|
495
|
+
| Export | Signature | Returns | Use it when |
|
|
496
|
+
| ------------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
497
|
+
| `METADATA_RELATIONSHIP_KINDS` | `const METADATA_RELATIONSHIP_KINDS` | — | The relationship kinds that are dependencies, and therefore citable. |
|
|
498
|
+
| `metadataFileName` | `function metadataFileName(pkg)` | {string} The file name, e.g. | What a package's content index is called, wherever it is written or fetched. |
|
|
499
|
+
| `metadataRelationships` | `function metadataRelationships(config)` | {Array<{id: string, manifest: string, kind: string, verified: string\|undefined}>} The dependencies, in declaration order. | Every dependency whose published index this build resolves addresses through. |
|
|
500
|
+
| `noContentIndexPackages` | `function noContentIndexPackages(config)` | {ReadonlySet<string>} The content package names. | Every package a relationship declares `contentIndex: false` on, keyed by the content package name a link into it would use. |
|
|
501
|
+
| `metadataCacheDir` | `function metadataCacheDir(config, id, version)` | {string} The directory. | The cache directory for one dependency's index at one version. |
|
|
502
|
+
| `isComplete` | `const isComplete` | {boolean} True when it was fetched to completion. | Whether a dependency's cache is present and complete. |
|
|
503
|
+
| `markComplete` | `function markComplete(dir)` | {void} | Mark a dependency's cache complete. |
|
|
504
|
+
| `cachedMetadataFiles` | `function cachedMetadataFiles(config)` | {string[]} One index file per declared dependency. | The fetched index files this build resolves foreign addresses against. |
|
|
505
|
+
| `cachedMetadataIndexes` | `function cachedMetadataIndexes(config)` | {Array<{id: string, file: string}>} One entry per declared dependency. | The same fetched indexes, each paired with the package that published it. |
|
|
506
|
+
| `newestVersionDir` | `function newestVersionDir(dirs)` | {string} The newest one. | The newest cached version among several version-keyed cache directories. |
|
|
507
|
+
| `loadForeignIndexes` | `function loadForeignIndexes(config, localPackages, bases` | {{index: Map<string, object>, packages: Set<string>, stale: Array<{package: string, reason: string}>}} The resolved addresses, which packages contributed, and what could not be read. | Resolve every foreign address this build can cite, from the fetched indexes. |
|
|
508
|
+
| `cachedIndexPath` | `function cachedIndexPath(config, pkg)` | {string} A path to name in a diagnostic. | Where a dependency's fetched index sits, for naming it in a diagnostic. |
|
|
509
|
+
| `unaddressableForeignPackages` | `function unaddressableForeignPackages(foreignIndex)` | {Array<{package: string, entries: number, sampleKey: string}>} One finding per drifted package, in the order the index first names each. | Whether a fetched index can still be _addressed_, as distinct from read. |
|
|
510
|
+
| `formatUnaddressableFinding` | `function formatUnaddressableFinding(finding, config)` | {string} The formatted diagnostic, path first on the line. | One finding, in the standard `file:line:column: severity: message` form. |
|
|
509
511
|
|
|
510
512
|
### `engine.foundryEntries`
|
|
511
513
|
|
|
@@ -834,14 +836,14 @@ Shared helpers for the pack compilers in `packages/content-build/`. The HeroicLa
|
|
|
834
836
|
|
|
835
837
|
Wikilink resolution for the pack compilers. Content notes link to one another with wikilinks rather than file paths:
|
|
836
838
|
|
|
837
|
-
| Export | Signature | Returns
|
|
838
|
-
| -------------------- | ----------------------------------------------------------------------- |
|
|
839
|
-
| `resolveItemDocType` | `function resolveItemDocType(qualifier, types)` | {string\|null} The underlying document type, or `null` when the qualifier is not a virtual one.
|
|
840
|
-
| `readQualifier` | `function readQualifier(target, types, packages)`
|
|
841
|
-
| `anchorPageId` | `function anchorPageId(noteId, anchorSlug)` | {string} A 16-character alphanumeric id.
|
|
842
|
-
| `buildWikilinkIndex` | `function buildWikilinkIndex(docs, packageId, foreign, contentPackage)` | {{byShortcode: Map<string, object>, types: Set<string>}} `types` is every type the tree actually contains, so a qualifier naming no real type can be told apart from a missing target.
|
|
843
|
-
| `resolveReference` | `function resolveReference(index, ref, hint)` | {{name?: string, uuid?: string, address?: string, subType?: string}\|undefined} The target, or `undefined` where nothing answers.
|
|
844
|
-
| `convertWikilinks` | `function convertWikilinks(markdown,` | {{markdown: string, unresolved: Array<{link: string, target: string, offset: number, reason: string, packages?: string[], anchor?: string}>}} Each `reason` is one of {@link LINK_FINDING_REASONS}, the vocabulary all three resolvers share — `ambiguous` carries the claiming `packages` and `unknown-anchor` the section it named.
|
|
839
|
+
| Export | Signature | Returns | Use it when |
|
|
840
|
+
| -------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
841
|
+
| `resolveItemDocType` | `function resolveItemDocType(qualifier, types)` | {string\|null} The underlying document type, or `null` when the qualifier is not a virtual one. | Reads a qualifier as the **virtual `doc<type>`** form, or reports that it is not one. |
|
|
842
|
+
| `readQualifier` | `function readQualifier(target, types, packages, noIndexPackages)` | {{type: string, shortcode: string, itemDoc: boolean, package?: string, system?: string, reason?: undefined} \| {reason: "unknown-type"\|"no-content-index", package?: string} \| null} The resolved qualifier; a `reason` when the target is definitely qualified but names no known type or no fetched index; or `null` when it is not an address at all. | Read a link target as a **qualified** `type-shortcode` reference, or report that it does not parse as one. |
|
|
843
|
+
| `anchorPageId` | `function anchorPageId(noteId, anchorSlug)` | {string} A 16-character alphanumeric id. | The deterministic JournalEntryPage id for one anchor: SHA-256 of `"<noteId>-<anchorSlug>"`, base64-encoded, reduced to the 16 alphanumeric characters a Foundry id allows. |
|
|
844
|
+
| `buildWikilinkIndex` | `function buildWikilinkIndex(docs, packageId, foreign, contentPackage)` | {{byShortcode: Map<string, object>, types: Set<string>}} `types` is every type the tree actually contains, so a qualifier naming no real type can be told apart from a missing target. | Builds the link-resolution tables for a content tree. |
|
|
845
|
+
| `resolveReference` | `function resolveReference(index, ref, hint)` | {{name?: string, uuid?: string, address?: string, subType?: string}\|undefined} The target, or `undefined` where nothing answers. | Resolve one reference — a bare shortcode, a short address or a canonical one — to what a compendium can use. |
|
|
846
|
+
| `convertWikilinks` | `function convertWikilinks(markdown,` | {{markdown: string, unresolved: Array<{link: string, target: string, offset: number, reason: string, packages?: string[], anchor?: string}>}} Each `reason` is one of {@link LINK_FINDING_REASONS}, the vocabulary all three resolvers share — `ambiguous` carries the claiming `packages` and `unknown-anchor` the section it named. | Rewrites every wikilink in a markdown body as a Foundry UUID enricher. |
|
|
845
847
|
|
|
846
848
|
### `engine.wikilinkSyntax`
|
|
847
849
|
|
package/docs/configuration.md
CHANGED
|
@@ -956,24 +956,26 @@ the block cannot grow into a second configuration file:
|
|
|
956
956
|
`content-build site` writes `build/hugo/hugo.toml` on every run. Every value
|
|
957
957
|
in it has one source, and that source is where it is edited:
|
|
958
958
|
|
|
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
|
-
| `
|
|
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` | 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
|
+
| `taxonomies` | the same fact — written as `{ tag = "tags" }` when at least one note carries `tags:`, absent otherwise |
|
|
970
|
+
| `outputs` | the same fact — written as `{ taxonomy = ["HTML"], term = ["HTML"] }` when at least one note carries `tags:`, absent otherwise |
|
|
971
|
+
| `params.description` | `package.json` `description`; absent when the package declares none |
|
|
972
|
+
| `params.author` | `package.json` `author`, its `name`; absent when the package declares none |
|
|
973
|
+
| `params.cdnBaseURL` | `site.assets`; absent when unset |
|
|
974
|
+
| `params.brand` | the organisation's brand links — `logo`, `licenseURL`, `discordURL` — in `engine/site-config.mjs` |
|
|
975
|
+
| `params.list` | `site.list` |
|
|
976
|
+
| `params.notfound` | `site.notfound`; absent when undeclared |
|
|
977
|
+
| `markup.goldmark.renderer.unsafe` | the toolchain, whose pages carry raw HTML — a `<figure>` for every image, a `<span>` marking an unresolved link |
|
|
978
|
+
| `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
979
|
|
|
978
980
|
The site build reads the navigation from the cache only. A cold cache is an
|
|
979
981
|
error naming the command that fills it:
|
|
@@ -989,9 +991,13 @@ none fails the site build:
|
|
|
989
991
|
|
|
990
992
|
> ``package-build config: `packageBuild.manifest.title` is not declared, and the site's `title` reads from it.``
|
|
991
993
|
|
|
992
|
-
Nothing else is emitted.
|
|
993
|
-
|
|
994
|
-
`
|
|
994
|
+
Nothing else is emitted. A site whose notes carry no `tags:` publishes no
|
|
995
|
+
taxonomy pages — `[taxonomies]` and `[outputs]` go unwritten, and Hugo's
|
|
996
|
+
defaults never apply because `taxonomy` and `term` are disabled kinds. A site
|
|
997
|
+
with at least one tagged note publishes `/tags/` and a page per tag: Hugo's
|
|
998
|
+
own default taxonomy pair also declares `category`, so `[taxonomies]` names
|
|
999
|
+
only `tag`, and `[outputs]` restricts both to `HTML` so neither produces a
|
|
1000
|
+
feed. Every other key is `site.hugo`'s to add.
|
|
995
1001
|
|
|
996
1002
|
### `pdf`
|
|
997
1003
|
|
|
@@ -1149,6 +1155,7 @@ Each entry, in any of the four lists:
|
|
|
1149
1155
|
| `relationships.systems[].manifest` | string | no | none |
|
|
1150
1156
|
| `relationships.systems[].compatibility` | object, `{minimum?, verified?}` | no | none |
|
|
1151
1157
|
| `relationships.systems[].itemCatalog` | boolean | no | `false` |
|
|
1158
|
+
| `relationships.systems[].contentIndex` | boolean | no | `true` |
|
|
1152
1159
|
|
|
1153
1160
|
(the same keys apply under `requires[]`, `recommends[]` and
|
|
1154
1161
|
`conflicts[]`.)
|
|
@@ -1157,7 +1164,7 @@ Each entry, in any of the four lists:
|
|
|
1157
1164
|
|
|
1158
1165
|
> ``package-build config: `relationships.<kind>[<index>].id` must be a non-empty string.``
|
|
1159
1166
|
|
|
1160
|
-
> ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog).``
|
|
1167
|
+
> ``package-build config: `relationships.<kind>[<index>].<key>` is not a recognized option (expected one of: id, contentPackage, type, manifest, compatibility, itemCatalog, contentIndex).``
|
|
1161
1168
|
|
|
1162
1169
|
`contentPackage` names what the other package's _content_ is called, where
|
|
1163
1170
|
that differs from its Foundry id. A note addresses a file by the content
|
|
@@ -1176,6 +1183,20 @@ item catalogue at build time. It requires a `manifest`:
|
|
|
1176
1183
|
|
|
1177
1184
|
> ``package-build config: `relationships.<kind>[<index>].itemCatalog` needs a `manifest` naming the package to fetch.``
|
|
1178
1185
|
|
|
1186
|
+
`contentIndex` and `itemCatalog` are the two edges a relationship may declare,
|
|
1187
|
+
and a package may have either without the other. `itemCatalog` says a
|
|
1188
|
+
dependency supplies _items_; `contentIndex`, `true` by default, says
|
|
1189
|
+
`deps fetch` fetches its published note index and this tree may cite its
|
|
1190
|
+
addresses by wikilink. Declaring `contentIndex: false` narrows the
|
|
1191
|
+
relationship to the Foundry manifest only — a dependency Foundry installs but
|
|
1192
|
+
this tree never cites — so `deps fetch` fetches nothing for it and a wikilink
|
|
1193
|
+
into it fails, naming the key, rather than resolving against a stale
|
|
1194
|
+
declaration or an index nobody fetched:
|
|
1195
|
+
|
|
1196
|
+
> ``package-build config: `relationships.<kind>[<index>].contentIndex` must be true or false.``
|
|
1197
|
+
|
|
1198
|
+
> ``package-build config: `relationships.<kind>[<index>].contentIndex` cannot be false together with `itemCatalog: true` — a catalogue is fetched from the same index.``
|
|
1199
|
+
|
|
1179
1200
|
### `systems`
|
|
1180
1201
|
|
|
1181
1202
|
**Type:** object (`{id: spec}`) · **Optional** · default `{}`. Refused in a
|
|
@@ -1647,17 +1668,17 @@ the source directory:
|
|
|
1647
1668
|
|
|
1648
1669
|
## Every retired or forbidden key, in one place
|
|
1649
1670
|
|
|
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`
|
|
1671
|
+
| Key | Why |
|
|
1672
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1673
|
+
| `publish.address.landing` | Retired — named a whole-section landing, and there are no sections to address. |
|
|
1674
|
+
| `packs[].folders` | Retired — a folder is a note (`type: folder`), materialised by the pack whose documents reference it. |
|
|
1675
|
+
| `rootDir` | Forbidden in a YAML configuration — always the file's own directory. |
|
|
1676
|
+
| `foundryPackage` | Forbidden in a YAML configuration — always the adjacent `package.json` `name`. |
|
|
1677
|
+
| `homepage`, `author` | Forbidden in a YAML configuration — always the adjacent `package.json`'s own `homepage` and `author`. |
|
|
1678
|
+
| `stats.systemId` | Forbidden in every configuration — derived from `packageKind`, `requiresSystem` or a lone declared system. |
|
|
1679
|
+
| `stats.systemVersion` | Forbidden in every configuration — derived from `package.json` (a system) or `systems:` / `relationships.systems` (a module). |
|
|
1680
|
+
| `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). |
|
|
1681
|
+
| `site.out` | Retired — the site build writes its content mount at `build/hugo/content`, beside the generated `hugo.toml`. |
|
|
1682
|
+
| `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). |
|
|
1683
|
+
| `publish.site: true` / `publish.site: false` | Refused rather than mapped — write `homepage` or `content`. |
|
|
1684
|
+
| `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
|
*
|