@heroiclands/package-build 22.0.3 → 22.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/CONTENT.md +34 -18
- package/README.md +1 -1
- package/bin/content-build.mjs +40 -22
- package/bin/package-build.mjs +3 -2
- package/config.mjs +52 -1
- package/content-config.mjs +235 -8
- package/docs/api.md +8 -7
- package/docs/commands.md +68 -32
- package/docs/configuration.md +254 -61
- package/docs/getting-started.md +15 -4
- package/docs/project-setup.md +45 -20
- package/engine/pack-config.mjs +21 -2
- package/engine/site-build.mjs +15 -52
- package/engine/site-config.mjs +462 -0
- package/manifest.mjs +12 -7
- package/package.json +2 -1
- package/stage.mjs +4 -3
- package/types/config.d.mts +24 -0
- package/types/content-config.d.mts +53 -0
- package/types/engine/site-build.d.mts +3 -25
- package/types/engine/site-config.d.mts +223 -0
- package/types/manifest.d.mts +4 -4
- package/types/stage.d.mts +4 -3
package/content-config.mjs
CHANGED
|
@@ -175,6 +175,10 @@ export const DEFAULT_PATHS = /** @type {const} */ ({
|
|
|
175
175
|
// dependency — so nesting one under the other would imply a containment
|
|
176
176
|
// that does not hold.
|
|
177
177
|
metadataCache: "build/cache/metadata",
|
|
178
|
+
// Where the site navigation heroiclands.org publishes is fetched to.
|
|
179
|
+
// Beside the other two: it is fetched by the same command and read under
|
|
180
|
+
// the same complete-marker rule.
|
|
181
|
+
navigationCache: "build/cache/navigation",
|
|
178
182
|
});
|
|
179
183
|
|
|
180
184
|
/**
|
|
@@ -387,6 +391,8 @@ export function publishesContentPages(config) {
|
|
|
387
391
|
* index is fetched to. Inbound,
|
|
388
392
|
* for *every* declared dependency, not
|
|
389
393
|
* only those supplying a catalogue.
|
|
394
|
+
* @property {string} [navigationCache] Where the site navigation is fetched
|
|
395
|
+
* to, for the generated Hugo menu.
|
|
390
396
|
*/
|
|
391
397
|
|
|
392
398
|
/**
|
|
@@ -401,6 +407,7 @@ export function publishesContentPages(config) {
|
|
|
401
407
|
* @property {string} unpack
|
|
402
408
|
* @property {string} foreignCache
|
|
403
409
|
* @property {string} metadataCache
|
|
410
|
+
* @property {string} navigationCache
|
|
404
411
|
*/
|
|
405
412
|
|
|
406
413
|
/**
|
|
@@ -578,6 +585,13 @@ export function publishesContentPages(config) {
|
|
|
578
585
|
* Refused by a `documentation`
|
|
579
586
|
* package, which ships no Foundry
|
|
580
587
|
* package.
|
|
588
|
+
* @property {string} [homepage] `package.json`'s own `homepage` —
|
|
589
|
+
* the site build's `baseURL`. Checked
|
|
590
|
+
* by `checkHomepage` in
|
|
591
|
+
* `config.mjs`.
|
|
592
|
+
* @property {string|{name: string, email?: string, url?: string}} [author]
|
|
593
|
+
* `package.json`'s own `author`, in
|
|
594
|
+
* either of npm's forms.
|
|
581
595
|
* @property {PackageKind} packageKind Whether the package is a system, a
|
|
582
596
|
* module, or documentation — the kind
|
|
583
597
|
* that publishes a site and a book
|
|
@@ -637,6 +651,13 @@ export function publishesContentPages(config) {
|
|
|
637
651
|
* @property {string} contentPackage
|
|
638
652
|
* @property {string|null} foundryPackage `null` for a `documentation`
|
|
639
653
|
* package, which ships no Foundry package.
|
|
654
|
+
* @property {string|null} homepage `package.json`'s own `homepage`,
|
|
655
|
+
* checked by `checkHomepage` in
|
|
656
|
+
* `config.mjs`.
|
|
657
|
+
* @property {Readonly<{name: string, email?: string, url?: string}>|null} author
|
|
658
|
+
* `package.json`'s own `author`, normalised
|
|
659
|
+
* from either of npm's forms; `null` when
|
|
660
|
+
* the package declares none.
|
|
640
661
|
* @property {PackageKind} packageKind
|
|
641
662
|
* @property {string|null} assetRoot Derived, and **conditional**: the served
|
|
642
663
|
* Foundry asset root,
|
|
@@ -705,6 +726,8 @@ const CONFIG_KEYS = [
|
|
|
705
726
|
"rootDir",
|
|
706
727
|
"contentPackage",
|
|
707
728
|
"foundryPackage",
|
|
729
|
+
"homepage",
|
|
730
|
+
"author",
|
|
708
731
|
"packageKind",
|
|
709
732
|
"stats",
|
|
710
733
|
"itemBuilders",
|
|
@@ -726,7 +749,6 @@ const SYSTEM_KEYS = ["manifest", "compatibility"];
|
|
|
726
749
|
const COMPATIBILITY_KEYS = ["minimum", "verified"];
|
|
727
750
|
const DOCS_KEYS = ["itemFields"];
|
|
728
751
|
const SITE_KEYS = [
|
|
729
|
-
"out",
|
|
730
752
|
"base",
|
|
731
753
|
"assets",
|
|
732
754
|
"packages",
|
|
@@ -737,8 +759,14 @@ const SITE_KEYS = [
|
|
|
737
759
|
"pass",
|
|
738
760
|
"passOptions",
|
|
739
761
|
"backfillSections",
|
|
762
|
+
"list",
|
|
763
|
+
"notfound",
|
|
764
|
+
"hugo",
|
|
740
765
|
];
|
|
741
766
|
const SITE_TREE_KEYS = ["from", "section"];
|
|
767
|
+
const SITE_LIST_KEYS = ["shortcodes"];
|
|
768
|
+
const SITE_NOTFOUND_KEYS = ["tagline", "sitenoun", "heroimage", "links"];
|
|
769
|
+
const SITE_NOTFOUND_LINK_KEYS = ["title", "url", "text"];
|
|
742
770
|
const PDF_KEYS = ["title", "subtitle", "document", "out", "front", "fonts", "iconFonts", "binary"];
|
|
743
771
|
const PDF_FONT_KEYS = ["serif", "sans", "mono", "path"];
|
|
744
772
|
const EMPTY_PDF_FONTS = Object.freeze({ serif: "", sans: "", mono: "", path: "" });
|
|
@@ -753,6 +781,7 @@ const RELATIONSHIP_KEYS = [
|
|
|
753
781
|
"compatibility",
|
|
754
782
|
"itemCatalog",
|
|
755
783
|
];
|
|
784
|
+
const AUTHOR_KEYS = ["name", "email", "url"];
|
|
756
785
|
const ITEM_BUILDER_KEYS = ["system", "img", "fields"];
|
|
757
786
|
const ITEM_REGISTRY_KEYS = ["system", "builders"];
|
|
758
787
|
const PACK_KEYS = [
|
|
@@ -964,6 +993,56 @@ function optionalString(value, field) {
|
|
|
964
993
|
return value;
|
|
965
994
|
}
|
|
966
995
|
|
|
996
|
+
/**
|
|
997
|
+
* npm's `author` field, in either of its two forms.
|
|
998
|
+
*
|
|
999
|
+
* `package.json` accepts a single string — `"Name <email> (url)"`, with the
|
|
1000
|
+
* email and the URL both optional — or an object carrying the same three
|
|
1001
|
+
* parts. Both normalise to one shape, so the site build reads one field
|
|
1002
|
+
* instead of branching on which form a repository happened to write.
|
|
1003
|
+
*
|
|
1004
|
+
* @type {RegExp}
|
|
1005
|
+
*/
|
|
1006
|
+
const AUTHOR_STRING = /^([^<(]*?)\s*(?:<([^>]*)>)?\s*(?:\(([^)]*)\))?\s*$/;
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* @param {unknown} value - The declared `author`, or `undefined`.
|
|
1010
|
+
* @returns {Readonly<{name: string, email?: string, url?: string}>|null}
|
|
1011
|
+
* `null` when the package declares none.
|
|
1012
|
+
*/
|
|
1013
|
+
function normalizeAuthor(value) {
|
|
1014
|
+
if (value === undefined) return null;
|
|
1015
|
+
if (typeof value === "string") {
|
|
1016
|
+
const match = AUTHOR_STRING.exec(value.trim());
|
|
1017
|
+
const name = match?.[1]?.trim();
|
|
1018
|
+
if (!match || !name) {
|
|
1019
|
+
fail(
|
|
1020
|
+
"author",
|
|
1021
|
+
'must be `"Name"`, `"Name <email>"`, `"Name (url)"` or ' +
|
|
1022
|
+
'`"Name <email> (url)"` — npm\'s own `author` forms',
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
return Object.freeze({
|
|
1026
|
+
name: /** @type {string} */ (name),
|
|
1027
|
+
...(match[2] ? { email: match[2] } : {}),
|
|
1028
|
+
...(match[3] ? { url: match[3] } : {}),
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
if (!isPlainObject(value)) {
|
|
1032
|
+
fail("author", "must be a string or an object with `name`, `email` and `url`");
|
|
1033
|
+
}
|
|
1034
|
+
const author = /** @type {Record<string, unknown>} */ (value);
|
|
1035
|
+
rejectUnknownKeys(author, AUTHOR_KEYS, "author.");
|
|
1036
|
+
const name = requireNonEmptyString(author.name, "author.name");
|
|
1037
|
+
return Object.freeze({
|
|
1038
|
+
name,
|
|
1039
|
+
...(author.email !== undefined ?
|
|
1040
|
+
{ email: optionalString(author.email, "author.email") }
|
|
1041
|
+
: {}),
|
|
1042
|
+
...(author.url !== undefined ? { url: optionalString(author.url, "author.url") } : {}),
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
967
1046
|
/**
|
|
968
1047
|
* @param {unknown} value
|
|
969
1048
|
* @param {string} where Field path used in error messages.
|
|
@@ -1504,21 +1583,149 @@ function normalizeSectionMap(value, where) {
|
|
|
1504
1583
|
return Object.freeze(out);
|
|
1505
1584
|
}
|
|
1506
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* Hugo keys a repository may **not** declare under `site.hugo`, because the
|
|
1588
|
+
* site build generates them and would only overwrite what was written.
|
|
1589
|
+
*
|
|
1590
|
+
* The same rule `DERIVED_MANIFEST_KEYS` states for the manifest, for the same
|
|
1591
|
+
* reason: an authored `baseURL` would look authoritative, sit there unread,
|
|
1592
|
+
* and disagree with the site forever. Each key names where its value comes
|
|
1593
|
+
* from. A dotted key names a nested one, and covers everything beneath it —
|
|
1594
|
+
* `params.brand` refuses `params.brand.logo` too — so `site.hugo` reaches only
|
|
1595
|
+
* what the generator does not write.
|
|
1596
|
+
*
|
|
1597
|
+
* @type {Readonly<Record<string, string>>}
|
|
1598
|
+
*/
|
|
1599
|
+
export const DERIVED_HUGO_KEYS = Object.freeze({
|
|
1600
|
+
baseURL: "package.json `homepage`",
|
|
1601
|
+
title: "`packageBuild.manifest.title`",
|
|
1602
|
+
locale: "the organisation's locale, in `engine/site-config.mjs`",
|
|
1603
|
+
publishDir: "`contentPackage`, under the deployment root `build/site`",
|
|
1604
|
+
contentDir: "the fixed content mount, `build/hugo/content`",
|
|
1605
|
+
themesDir: "where `@heroiclands/hugo-theme` is installed",
|
|
1606
|
+
theme: "the installed `@heroiclands/hugo-theme`",
|
|
1607
|
+
disableKinds: "the toolchain, which renders the same kinds on every site",
|
|
1608
|
+
"params.description": "package.json `description`",
|
|
1609
|
+
"params.author": "package.json `author`",
|
|
1610
|
+
"params.cdnBaseURL": "`site.assets`",
|
|
1611
|
+
"params.brand": "the organisation's brand links, in `engine/site-config.mjs`",
|
|
1612
|
+
"params.list": "`site.list`",
|
|
1613
|
+
"params.notfound": "`site.notfound`",
|
|
1614
|
+
"markup.goldmark.renderer.unsafe": "the toolchain, whose pages carry raw HTML",
|
|
1615
|
+
menu: "the navigation `content-build deps fetch` caches from heroiclands.org",
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* The `site.list` block — how a listing page renders.
|
|
1620
|
+
*
|
|
1621
|
+
* @param {unknown} value - The block, or `undefined`.
|
|
1622
|
+
* @returns {Readonly<{shortcodes: boolean}>} It, frozen, with every default filled.
|
|
1623
|
+
*/
|
|
1624
|
+
function normalizeSiteList(value) {
|
|
1625
|
+
if (value === undefined) return Object.freeze({ shortcodes: false });
|
|
1626
|
+
if (!isPlainObject(value)) fail("site.list", "must be a mapping");
|
|
1627
|
+
const input = /** @type {Record<string, unknown>} */ (value);
|
|
1628
|
+
rejectUnknownKeys(input, SITE_LIST_KEYS, "site.list.");
|
|
1629
|
+
return Object.freeze({
|
|
1630
|
+
shortcodes: optionalBoolean(input.shortcodes, "site.list.shortcodes", false),
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* The `site.notfound` block — the wording of the "page not found" page.
|
|
1636
|
+
*
|
|
1637
|
+
* The theme renders the page for every site; what a repository supplies is
|
|
1638
|
+
* the tagline, the noun the body prose calls the site, and the routes back.
|
|
1639
|
+
* `tagline` and `sitenoun` are required once the block is present: a block
|
|
1640
|
+
* declaring only links would render the theme's generic wording above a list
|
|
1641
|
+
* of this site's routes, which reads as two sites.
|
|
1642
|
+
*
|
|
1643
|
+
* @param {unknown} value - The block, or `undefined`.
|
|
1644
|
+
* @returns {Readonly<object>|null} It, frozen; `null` when absent.
|
|
1645
|
+
*/
|
|
1646
|
+
function normalizeSiteNotfound(value) {
|
|
1647
|
+
if (value === undefined) return null;
|
|
1648
|
+
if (!isPlainObject(value)) fail("site.notfound", "must be a mapping");
|
|
1649
|
+
const input = /** @type {Record<string, unknown>} */ (value);
|
|
1650
|
+
rejectUnknownKeys(input, SITE_NOTFOUND_KEYS, "site.notfound.");
|
|
1651
|
+
const out = {
|
|
1652
|
+
tagline: requireNonEmptyString(input.tagline, "site.notfound.tagline"),
|
|
1653
|
+
sitenoun: requireNonEmptyString(input.sitenoun, "site.notfound.sitenoun"),
|
|
1654
|
+
};
|
|
1655
|
+
if (input.heroimage !== undefined) {
|
|
1656
|
+
out.heroimage = requireNonEmptyString(input.heroimage, "site.notfound.heroimage");
|
|
1657
|
+
}
|
|
1658
|
+
if (input.links !== undefined) {
|
|
1659
|
+
if (!Array.isArray(input.links)) fail("site.notfound.links", "must be a list");
|
|
1660
|
+
out.links = Object.freeze(
|
|
1661
|
+
input.links.map((link, i) => {
|
|
1662
|
+
const where = `site.notfound.links[${i}]`;
|
|
1663
|
+
if (!isPlainObject(link)) fail(where, "must be a mapping");
|
|
1664
|
+
const entry = /** @type {Record<string, unknown>} */ (link);
|
|
1665
|
+
rejectUnknownKeys(entry, SITE_NOTFOUND_LINK_KEYS, `${where}.`);
|
|
1666
|
+
return Object.freeze({
|
|
1667
|
+
title: requireNonEmptyString(entry.title, `${where}.title`),
|
|
1668
|
+
url: requireNonEmptyString(entry.url, `${where}.url`),
|
|
1669
|
+
text: requireNonEmptyString(entry.text, `${where}.text`),
|
|
1670
|
+
});
|
|
1671
|
+
}),
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
return Object.freeze(out);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* The `site.hugo` block — a mapping deep-merged over the generated Hugo
|
|
1679
|
+
* configuration, last.
|
|
1680
|
+
*
|
|
1681
|
+
* The escape hatch for the key nobody anticipated. Every key the generator
|
|
1682
|
+
* writes is refused here by {@link DERIVED_HUGO_KEYS}, naming its source, so
|
|
1683
|
+
* the block cannot grow into a second configuration file.
|
|
1684
|
+
*
|
|
1685
|
+
* @param {unknown} value - The block, or `undefined`.
|
|
1686
|
+
* @returns {Readonly<Record<string, unknown>>} It, frozen; `{}` when absent.
|
|
1687
|
+
*/
|
|
1688
|
+
function normalizeSiteHugo(value) {
|
|
1689
|
+
if (value === undefined) return Object.freeze({});
|
|
1690
|
+
if (!isPlainObject(value)) fail("site.hugo", "must be a mapping");
|
|
1691
|
+
const input = /** @type {Record<string, unknown>} */ (value);
|
|
1692
|
+
for (const [dotted, source] of Object.entries(DERIVED_HUGO_KEYS)) {
|
|
1693
|
+
/** @type {unknown} */
|
|
1694
|
+
let at = input;
|
|
1695
|
+
for (const part of dotted.split(".")) {
|
|
1696
|
+
at = isPlainObject(at) ? /** @type {Record<string, unknown>} */ (at)[part] : undefined;
|
|
1697
|
+
if (at === undefined) break;
|
|
1698
|
+
}
|
|
1699
|
+
if (at !== undefined) {
|
|
1700
|
+
fail(
|
|
1701
|
+
`site.hugo.${dotted}`,
|
|
1702
|
+
`is derived from ${source} and must not be declared — it ` +
|
|
1703
|
+
`would be overwritten, and the two would disagree with ` +
|
|
1704
|
+
`nothing to say so`,
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return Object.freeze(structuredClone(input));
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1507
1711
|
/**
|
|
1508
1712
|
* The `site` section — how this repository frames the website it publishes.
|
|
1509
1713
|
*
|
|
1510
|
-
* Everything here is *framing*:
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1714
|
+
* Everything here is *framing*: what a section is called, which extra trees
|
|
1715
|
+
* are published beside the content, which named pass bundle supplies the
|
|
1716
|
+
* repository's own body rewrites, and the residue of the generated Hugo
|
|
1717
|
+
* configuration that is genuinely this repository's own. Where the Hugo tree
|
|
1718
|
+
* is written is not a choice: `content-build site` writes it under
|
|
1719
|
+
* `build/hugo/`, and a `site.out` is refused by name. How a page gets its
|
|
1720
|
+
* **address** is deliberately not here either — that is `publish.address`,
|
|
1721
|
+
* shared with the link manifest so the two cannot disagree about where a
|
|
1722
|
+
* page is.
|
|
1515
1723
|
*
|
|
1516
1724
|
* @param {unknown} value - The `site` block, or `undefined`.
|
|
1517
1725
|
* @returns {Readonly<object>} It, frozen, with every default filled.
|
|
1518
1726
|
*/
|
|
1519
1727
|
function normalizeSite(value) {
|
|
1520
1728
|
const empty = Object.freeze({
|
|
1521
|
-
out: "",
|
|
1522
1729
|
base: "",
|
|
1523
1730
|
assets: "",
|
|
1524
1731
|
packages: Object.freeze([]),
|
|
@@ -1529,10 +1736,23 @@ function normalizeSite(value) {
|
|
|
1529
1736
|
pass: "",
|
|
1530
1737
|
passOptions: Object.freeze({}),
|
|
1531
1738
|
backfillSections: false,
|
|
1739
|
+
list: Object.freeze({ shortcodes: false }),
|
|
1740
|
+
notfound: null,
|
|
1741
|
+
hugo: Object.freeze({}),
|
|
1532
1742
|
});
|
|
1533
1743
|
if (value === undefined) return empty;
|
|
1534
1744
|
if (!isPlainObject(value)) fail("site", "must be a mapping");
|
|
1535
1745
|
const input = /** @type {Record<string, unknown>} */ (value);
|
|
1746
|
+
// Refused by name, ahead of the vocabulary check: the useful thing to say
|
|
1747
|
+
// is not "no such key" but that the location is fixed.
|
|
1748
|
+
if (input.out !== undefined) {
|
|
1749
|
+
fail(
|
|
1750
|
+
"site.out",
|
|
1751
|
+
"is retired — the site build writes its content mount at " +
|
|
1752
|
+
"`build/hugo/content`, beside the generated `hugo.toml`, and " +
|
|
1753
|
+
"the location is not configurable. Remove the key",
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1536
1756
|
rejectUnknownKeys(input, SITE_KEYS, "site.");
|
|
1537
1757
|
|
|
1538
1758
|
const trees = [];
|
|
@@ -1574,7 +1794,6 @@ function normalizeSite(value) {
|
|
|
1574
1794
|
}
|
|
1575
1795
|
|
|
1576
1796
|
return Object.freeze({
|
|
1577
|
-
out: input.out === undefined ? "" : requireNonEmptyString(input.out, "site.out"),
|
|
1578
1797
|
base: input.base === undefined ? "" : requireNonEmptyString(input.base, "site.base"),
|
|
1579
1798
|
assets: normalizeSiteAssets(input.assets),
|
|
1580
1799
|
packages: Object.freeze(packages),
|
|
@@ -1588,6 +1807,9 @@ function normalizeSite(value) {
|
|
|
1588
1807
|
Object.freeze({})
|
|
1589
1808
|
: Object.freeze({ ...input.passOptions }),
|
|
1590
1809
|
backfillSections: optionalBoolean(input.backfillSections, "site.backfillSections", false),
|
|
1810
|
+
list: normalizeSiteList(input.list),
|
|
1811
|
+
notfound: normalizeSiteNotfound(input.notfound),
|
|
1812
|
+
hugo: normalizeSiteHugo(input.hugo),
|
|
1591
1813
|
});
|
|
1592
1814
|
}
|
|
1593
1815
|
|
|
@@ -2493,6 +2715,11 @@ export function defineConfig(config) {
|
|
|
2493
2715
|
rootDir,
|
|
2494
2716
|
contentPackage: requireContentPackage(input.contentPackage, docEntryTypes),
|
|
2495
2717
|
foundryPackage,
|
|
2718
|
+
// `package.json`'s own address and byline. `homepage` is checked by
|
|
2719
|
+
// `checkHomepage` in `config.mjs`.
|
|
2720
|
+
homepage:
|
|
2721
|
+
input.homepage === undefined ? null : requireNonEmptyString(input.homepage, "homepage"),
|
|
2722
|
+
author: normalizeAuthor(input.author),
|
|
2496
2723
|
packageKind: /** @type {PackageKind} */ (packageKind),
|
|
2497
2724
|
// Foundry serves a package's files from `<kind>/<id>/`, so this is the
|
|
2498
2725
|
// one place `systems/sohl` (or `modules/sohl-thalorna`) is spelled.
|
package/docs/api.md
CHANGED
|
@@ -621,8 +621,7 @@ Publishing a content tree as a website. Compiling a content tree into compendium
|
|
|
621
621
|
| `writeSectionLandings` | `function writeSectionLandings(outRoot,` | {number} How many landings were written. | Writes the Hugo sections a published tree declares. |
|
|
622
622
|
| `pluralTitle` | `function pluralTitle(name)` | {string} The display title. | A section landing's title, from its directory name — `macro` → `Macros`. |
|
|
623
623
|
| `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function, afterLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
|
|
624
|
-
| `
|
|
625
|
-
| `buildSite` | `buildSite({ config, outRoot, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
|
|
624
|
+
| `buildSite` | `buildSite({ config, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
|
|
626
625
|
|
|
627
626
|
### `engine.contentLint`
|
|
628
627
|
|
|
@@ -1269,6 +1268,7 @@ const config = defineConfig({
|
|
|
1269
1268
|
| `SITE_MODES` | `const SITE_MODES` | — | reading the publishing modes `publish.site` may name, weakest first |
|
|
1270
1269
|
| `DERIVED_SYSTEM_VERSION` | `const DERIVED_SYSTEM_VERSION` | — | the loader-only symbol key `defineConfig` uses internally to receive a resolved system version; not something a configuration author writes |
|
|
1271
1270
|
| `publishesContentPages` | `publishesContentPages(config)` | `boolean` | checking whether a resolved configuration publishes the pages its content tree compiles to — the one question the site build and the content index both need answered identically |
|
|
1271
|
+
| `DERIVED_HUGO_KEYS` | `const DERIVED_HUGO_KEYS` | — | reading which Hugo keys `site.hugo` may not declare because the site build generates them, each naming its source (declaring one is an error naming the key); a dotted key covers everything beneath it |
|
|
1272
1272
|
|
|
1273
1273
|
## `./config`
|
|
1274
1274
|
|
|
@@ -1282,11 +1282,12 @@ const config = loadPackageBuildConfig();
|
|
|
1282
1282
|
console.log(config.stageDir);
|
|
1283
1283
|
```
|
|
1284
1284
|
|
|
1285
|
-
| Export | Signature
|
|
1286
|
-
| --------------------------- |
|
|
1287
|
-
| `DERIVED_MANIFEST_KEYS` | `const DERIVED_MANIFEST_KEYS`
|
|
1288
|
-
| `resolvePackageBuildConfig` | `resolvePackageBuildConfig(shared)`
|
|
1289
|
-
| `loadPackageBuildConfig` | `loadPackageBuildConfig()`
|
|
1285
|
+
| Export | Signature | Returns | Use it when |
|
|
1286
|
+
| --------------------------- | ----------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1287
|
+
| `DERIVED_MANIFEST_KEYS` | `const DERIVED_MANIFEST_KEYS` | — | reading which manifest keys a repository may not declare because the build derives them (declaring one is an error naming the key) |
|
|
1288
|
+
| `resolvePackageBuildConfig` | `resolvePackageBuildConfig(shared)` | `Readonly<PackageBuildConfig>` | validating the `packageBuild:` section from an already-loaded shared configuration — the pure half, usable without touching disk |
|
|
1289
|
+
| `loadPackageBuildConfig` | `loadPackageBuildConfig()` | `Readonly<PackageBuildConfig>` | reading and validating the repository's resolved package-build configuration from disk, read fresh on each call rather than cached at import |
|
|
1290
|
+
| `checkHomepage` | `checkHomepage(homepage, contentPackage)` | `void` | validating a resolved `homepage` against `contentPackage` — called by `content-build site` before the generated `baseURL` is written, not by `resolvePackageBuildConfig` itself |
|
|
1290
1291
|
|
|
1291
1292
|
## `./prettier`
|
|
1292
1293
|
|
package/docs/commands.md
CHANGED
|
@@ -987,17 +987,28 @@ content-build deps fetch [--from <zip|dir>] [--id <id>]
|
|
|
987
987
|
|
|
988
988
|
**DESCRIPTION**
|
|
989
989
|
|
|
990
|
-
The only action is `fetch`, which fills
|
|
991
|
-
declared dependency,
|
|
992
|
-
declare `itemCatalog: true
|
|
993
|
-
`
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
990
|
+
The only action is `fetch`, which fills three caches under `build/cache/`:
|
|
991
|
+
the **content index** of every declared dependency, the **item catalogue**
|
|
992
|
+
of those that additionally declare `itemCatalog: true`, and the **site
|
|
993
|
+
navigation** — `https://www.heroiclands.org/nav.json`, the header menu
|
|
994
|
+
every package site renders, fetched for every package because every package
|
|
995
|
+
publishes a site. Its own command rather than a step of `package compile`
|
|
996
|
+
or `site`, so neither reaches the network — a build that downloads silently
|
|
997
|
+
is not reproducible and hides a dependency's version change behind a
|
|
998
|
+
passing run. Each cache is stamped complete only once its fetch finishes,
|
|
999
|
+
so a half-finished one reads as cold.
|
|
1000
|
+
|
|
1001
|
+
The navigation is fetched first, because every package needs it and it
|
|
1002
|
+
depends on nothing a repository declares — so a dependency whose release
|
|
1003
|
+
cannot be read stops the run with the navigation already cached.
|
|
1004
|
+
|
|
1005
|
+
`--from` fills the cache from a locally built artifact — a package zip, or
|
|
1006
|
+
the directory it was built from — instead of a release, which is what makes
|
|
1007
|
+
testing a dependency change against its consumers possible before any of it
|
|
1008
|
+
ships; `--id` names which declared dependency `--from` supplies, needed only
|
|
1009
|
+
when the repository declares more than one. `--from` fills that one
|
|
1010
|
+
dependency's caches and nothing else: the navigation is not an artifact of
|
|
1011
|
+
any dependency, and is fetched by a plain `deps fetch`.
|
|
1001
1012
|
|
|
1002
1013
|
**OPTIONS**
|
|
1003
1014
|
|
|
@@ -1016,6 +1027,7 @@ including when the repository declares no dependencies at all.
|
|
|
1016
1027
|
|
|
1017
1028
|
```
|
|
1018
1029
|
$ content-build deps fetch
|
|
1030
|
+
[…] Fetched the site navigation to build/cache/navigation/nav.json.
|
|
1019
1031
|
[…] No relationship declares `itemCatalog: true`; nothing to fetch.
|
|
1020
1032
|
[…] This package declares no dependencies.
|
|
1021
1033
|
|
|
@@ -1025,7 +1037,7 @@ $ content-build deps fetch --from build/dist/module.zip
|
|
|
1025
1037
|
|
|
1026
1038
|
**SEE ALSO**
|
|
1027
1039
|
|
|
1028
|
-
`content-build addresses diff`, [Configuration](configuration.md).
|
|
1040
|
+
`content-build site`, `content-build addresses diff`, [Configuration](configuration.md).
|
|
1029
1041
|
|
|
1030
1042
|
### `content-build docs item-fields`
|
|
1031
1043
|
|
|
@@ -1515,54 +1527,78 @@ $ content-build content-index
|
|
|
1515
1527
|
|
|
1516
1528
|
**NAME**
|
|
1517
1529
|
|
|
1518
|
-
|
|
1530
|
+
Build the Hugo source tree from the content tree.
|
|
1519
1531
|
|
|
1520
1532
|
**SYNOPSIS**
|
|
1521
1533
|
|
|
1522
1534
|
```
|
|
1523
|
-
content-build site
|
|
1535
|
+
content-build site
|
|
1524
1536
|
```
|
|
1525
1537
|
|
|
1526
1538
|
**DESCRIPTION**
|
|
1527
1539
|
|
|
1528
|
-
|
|
1540
|
+
Writes the whole Hugo source tree under `build/hugo/` — the sibling of
|
|
1529
1541
|
`package compile`: the same tree, rendered as pages instead of compiled
|
|
1530
1542
|
into packs. Everything a consumer would otherwise write for itself happens
|
|
1531
1543
|
here: the walk, address derivation, the address index, table expansion,
|
|
1532
|
-
wikilink resolution, code-fence protection, the foreign-manifest merge
|
|
1533
|
-
|
|
1534
|
-
|
|
1544
|
+
wikilink resolution, code-fence protection, the foreign-manifest merge, the
|
|
1545
|
+
section-landing backfill, and the Hugo configuration itself. The consumer's
|
|
1546
|
+
script then runs Hugo over the tree — `hugo --source build/hugo` — and this
|
|
1547
|
+
command never does.
|
|
1548
|
+
|
|
1549
|
+
Three things are written, and nothing outside `build/`:
|
|
1550
|
+
|
|
1551
|
+
- `build/hugo/hugo.toml`, generated on every run from `package.json`
|
|
1552
|
+
(`homepage`, `description`, `author`), `package-build.config.yaml`
|
|
1553
|
+
(`packageBuild.manifest.title`, `site.assets`, `site.list`,
|
|
1554
|
+
`site.notfound`, `site.hugo`), the organisation's constants, the installed
|
|
1555
|
+
`@heroiclands/hugo-theme`'s location, and the navigation `deps fetch`
|
|
1556
|
+
cached. Every value's source is listed under
|
|
1557
|
+
[the generated Hugo configuration](configuration.md#the-generated-hugo-configuration).
|
|
1558
|
+
- `build/hugo/content/`, the content mount — the homepage at its root, and
|
|
1559
|
+
the content tree's pages below `publish.address.prefix`. Wiped on every
|
|
1560
|
+
run.
|
|
1561
|
+
- `publishDir` pointing Hugo at `build/site/<contentPackage>/`, the
|
|
1562
|
+
deployment root `package-build site-root` writes beside. Nothing Hugo
|
|
1563
|
+
reads lands in what is published.
|
|
1564
|
+
|
|
1565
|
+
The configuration's sources are read before the output tree is touched, so
|
|
1566
|
+
a missing `homepage`, a cold navigation cache or an uninstalled theme fails
|
|
1567
|
+
with the previous site intact. Every gate is then checked and reported, and
|
|
1568
|
+
the run stops at the first that fires, ordered so the report names the cause
|
|
1535
1569
|
rather than its symptoms — an unusable dependency manifest, reported after
|
|
1536
1570
|
the links that failed because of it, would otherwise read as a pile of
|
|
1537
|
-
broken notes. Reads the content tree named by `paths.content
|
|
1538
|
-
`--out`, defaulting to the configured `site.out`, which is wiped on every
|
|
1539
|
-
run.
|
|
1571
|
+
broken notes. Reads the content tree named by `paths.content`.
|
|
1540
1572
|
|
|
1541
1573
|
**OPTIONS**
|
|
1542
1574
|
|
|
1543
|
-
|
|
1544
|
-
| ------- | ------ | ------------------------- | -------------------------------------------------------- |
|
|
1545
|
-
| `--out` | string | the configured `site.out` | Write the mount here instead of the configured location. |
|
|
1575
|
+
None.
|
|
1546
1576
|
|
|
1547
1577
|
**EXIT STATUS**
|
|
1548
1578
|
|
|
1549
|
-
1 if `
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1579
|
+
1 if `package.json` declares no `homepage`, or one that does not end
|
|
1580
|
+
`/<contentPackage>/`; if `packageBuild.manifest.title` is undeclared; if the
|
|
1581
|
+
navigation has not been fetched (`content-build deps fetch` fills the cache
|
|
1582
|
+
and is named in the message); or if `@heroiclands/hugo-theme` is not
|
|
1583
|
+
installed. 1 if any gate fires — no homepage or two competing for it, a
|
|
1584
|
+
frontmatter wikilink, an address that cannot be derived, a stale or
|
|
1585
|
+
unaddressable dependency manifest, an address published twice, a table that
|
|
1586
|
+
failed to expand, or a dead wikilink. Otherwise 0.
|
|
1555
1587
|
|
|
1556
1588
|
**EXAMPLES**
|
|
1557
1589
|
|
|
1558
1590
|
```
|
|
1559
1591
|
$ content-build site
|
|
1560
|
-
[…] wrote 1 homepage(s) + 1 content page(s) + 0 tree page(s) + 0 landing(s) to build/
|
|
1592
|
+
[…] wrote 1 homepage(s) + 1 content page(s) + 0 tree page(s) + 0 landing(s) to build/hugo/content
|
|
1593
|
+
[…] wrote build/hugo/hugo.toml
|
|
1594
|
+
|
|
1595
|
+
$ content-build site
|
|
1596
|
+
[…] ERROR: the site navigation has not been fetched. Run `content-build deps fetch` first.
|
|
1561
1597
|
```
|
|
1562
1598
|
|
|
1563
1599
|
**SEE ALSO**
|
|
1564
1600
|
|
|
1565
|
-
`content-build package <action> [pack] [entry]`, `content-build pdf`, `content-build content-index [root]`,
|
|
1601
|
+
`content-build deps fetch`, `package-build site-root`, `content-build package <action> [pack] [entry]`, `content-build pdf`, `content-build content-index [root]`,
|
|
1566
1602
|
[Diagnostics](diagnostics.md), [Configuration](configuration.md).
|
|
1567
1603
|
|
|
1568
1604
|
### `content-build pdf`
|