@heroiclands/package-build 23.13.0 → 24.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +2 -1
- package/assets/preview/gitea.ini +12 -7
- package/assets/styles/content-base.css +150 -1
- package/bin/content-commands.mjs +356 -210
- package/bin/package-build.mjs +3 -2
- package/ci/check-banners.mjs +7 -7
- package/config.mjs +21 -6
- package/content-config.mjs +199 -80
- package/docs/api.md +93 -72
- package/docs/authoring/links-and-markup.md +316 -78
- package/docs/commands.md +244 -78
- package/docs/configuration.md +210 -102
- package/docs/content-format.md +2 -2
- package/docs/diagnostics.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/guides/icons.md +1 -1
- package/docs/guides/module-project.md +5 -8
- package/docs/guides/release-and-deploy.md +2 -2
- package/docs/guides/site.md +112 -23
- package/docs/guides/system-project.md +1 -1
- package/docs/guides/writing-in-invented-scripts.md +1247 -0
- package/docs/index.md +1 -0
- package/docs/project-setup.md +28 -30
- package/docs/reference/format-details.md +589 -308
- package/docs/reference/note-types.md +40 -10
- package/docs/reference/theme-pages.md +127 -108
- package/docs/source-preview.md +15 -1
- package/engine/asset-shipping.mjs +22 -10
- package/engine/base-compiler.mjs +62 -104
- package/engine/changelog-lint.mjs +1 -1
- package/engine/content-address.mjs +1 -1
- package/engine/content-alerts.mjs +8 -4
- package/engine/content-blocks.mjs +26 -11
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-expand.mjs +376 -0
- package/engine/content-figures.mjs +13 -6
- package/engine/content-format.yaml +8 -0
- package/engine/content-glyphs.mjs +213 -0
- package/engine/content-images.mjs +6 -7
- package/engine/content-links.mjs +3 -3
- package/engine/content-lint.mjs +1 -1
- package/engine/content-render.mjs +453 -0
- package/engine/content-scripts.mjs +326 -27
- package/engine/content-spans.mjs +4 -1
- package/engine/content-styles.mjs +91 -0
- package/engine/content-symbols.mjs +186 -0
- package/engine/content-verse.mjs +6 -18
- package/engine/core-report.mjs +169 -0
- package/engine/core-vocabulary.mjs +208 -0
- package/engine/dictionary-html.mjs +119 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/heading-attributes.mjs +89 -0
- package/engine/helpers.mjs +61 -101
- package/engine/homepage.mjs +17 -29
- package/engine/html-minify.mjs +70 -0
- package/engine/index.mjs +8 -5
- package/engine/language-rows.mjs +8 -2
- package/engine/language-terms.mjs +115 -14
- package/engine/language-words.mjs +12 -0
- package/engine/logographic.mjs +255 -0
- package/engine/map-lexicon.mjs +221 -25
- package/engine/map-places.mjs +5 -4
- package/engine/markdown-expressions.mjs +0 -2
- package/engine/markdownlint-run.mjs +9 -3
- package/engine/markup-validation.mjs +21 -0
- package/engine/metre-declaration.mjs +134 -0
- package/engine/note-matter.mjs +2 -2
- package/engine/note-vocabulary.mjs +187 -3
- package/engine/pdf-build.mjs +68 -133
- package/engine/pdf-render.mjs +102 -44
- package/engine/post-fields.mjs +157 -0
- package/engine/preview-index.mjs +130 -0
- package/engine/preview-source.mjs +450 -0
- package/engine/project-init.mjs +5 -12
- package/engine/prose-config.mjs +3 -3
- package/engine/run-words.mjs +3 -2
- package/engine/script-emphasis-terms.mjs +38 -0
- package/engine/script-emphasis.mjs +295 -0
- package/engine/script-font-delivery.mjs +4 -4
- package/engine/site-build.mjs +259 -314
- package/engine/site-config.mjs +26 -238
- package/engine/site-files.mjs +175 -0
- package/engine/site-index.mjs +9 -4
- package/engine/site-preview.mjs +50 -523
- package/engine/site-render.mjs +593 -0
- package/engine/site-root.mjs +22 -13
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +273 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +926 -0
- package/engine/text-maps.mjs +57 -10
- package/engine/typographer.mjs +248 -0
- package/engine/verse-metre.mjs +357 -0
- package/engine/wikilink-syntax.mjs +29 -47
- package/engine/wikilinks.mjs +758 -279
- package/engine/word-forms.mjs +39 -24
- package/engine/word-rows.mjs +12 -2
- package/engine/word-terms.mjs +29 -4
- package/manifest.mjs +3 -4
- package/package.json +10 -7
- package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
- package/site-theme/layouts/base.hbs +31 -0
- package/site-theme/layouts/blog-list.hbs +30 -0
- package/site-theme/layouts/blog-post.hbs +40 -0
- package/site-theme/layouts/home.hbs +35 -0
- package/site-theme/layouts/list.hbs +20 -0
- package/site-theme/layouts/not-found.hbs +31 -0
- package/site-theme/layouts/page.hbs +7 -0
- package/site-theme/partials/copy-buttons.hbs +37 -0
- package/site-theme/partials/draft-notice.hbs +5 -0
- package/site-theme/partials/exturl.hbs +2 -0
- package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
- package/site-theme/partials/header.hbs +44 -0
- package/site-theme/partials/hero-banner.hbs +11 -0
- package/site-theme/partials/infobox-entry.hbs +2 -0
- package/site-theme/partials/infobox-section.hbs +31 -0
- package/site-theme/partials/infobox-value.hbs +2 -0
- package/site-theme/partials/infobox.hbs +18 -0
- package/site-theme/partials/list-members.hbs +9 -0
- package/site-theme/partials/nav-links.hbs +5 -0
- package/site-theme/partials/page-body.hbs +48 -0
- package/site-theme/partials/page-list.hbs +14 -0
- package/site-theme/partials/related.hbs +24 -0
- package/site-theme/partials/search-meta.hbs +6 -0
- package/site-theme/partials/search-names.hbs +2 -0
- package/site-theme/partials/search.hbs +52 -0
- package/site-theme/partials/tags.hbs +4 -0
- package/site-theme/partials/toc.hbs +7 -0
- package/{hugo-theme → site-theme}/static/css/style.css +14 -0
- package/stage.mjs +1 -2
- package/types/config.d.mts +8 -4
- package/types/content-config.d.mts +15 -15
- package/types/engine/asset-shipping.d.mts +5 -2
- package/types/engine/base-compiler.d.mts +20 -2
- package/types/engine/content-address.d.mts +1 -1
- package/types/engine/content-alerts.d.mts +8 -2
- package/types/engine/content-blocks.d.mts +11 -4
- package/types/engine/content-expand.d.mts +288 -0
- package/types/engine/content-figures.d.mts +11 -3
- package/types/engine/content-glyphs.d.mts +85 -0
- package/types/engine/content-images.d.mts +6 -7
- package/types/engine/content-links.d.mts +1 -1
- package/types/engine/content-render.d.mts +199 -0
- package/types/engine/content-scripts.d.mts +62 -0
- package/types/engine/content-spans.d.mts +1 -1
- package/types/engine/content-styles.d.mts +33 -0
- package/types/engine/content-symbols.d.mts +35 -0
- package/types/engine/core-report.d.mts +45 -0
- package/types/engine/core-vocabulary.d.mts +53 -0
- package/types/engine/dictionary-html.d.mts +8 -0
- package/types/engine/field-reference.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +20 -0
- package/types/engine/helpers.d.mts +3 -10
- package/types/engine/homepage.d.mts +11 -24
- package/types/engine/html-minify.d.mts +9 -0
- package/types/engine/index.d.mts +2 -1
- package/types/engine/language-terms.d.mts +24 -2
- package/types/engine/logographic.d.mts +45 -0
- package/types/engine/map-lexicon.d.mts +48 -8
- package/types/engine/map-places.d.mts +8 -0
- package/types/engine/markdown-expressions.d.mts +0 -2
- package/types/engine/metre-declaration.d.mts +39 -0
- package/types/engine/pdf-render.d.mts +6 -2
- package/types/engine/post-fields.d.mts +44 -0
- package/types/engine/preview-index.d.mts +72 -0
- package/types/engine/preview-source.d.mts +51 -0
- package/types/engine/script-emphasis-terms.d.mts +21 -0
- package/types/engine/script-emphasis.d.mts +84 -0
- package/types/engine/script-font-delivery.d.mts +4 -4
- package/types/engine/site-build.d.mts +71 -113
- package/types/engine/site-config.d.mts +14 -134
- package/types/engine/site-files.d.mts +73 -0
- package/types/engine/site-index.d.mts +11 -3
- package/types/engine/site-preview.d.mts +15 -19
- package/types/engine/site-render.d.mts +171 -0
- package/types/engine/site-root.d.mts +12 -4
- package/types/engine/site-search.d.mts +54 -0
- package/types/engine/site-serve.d.mts +44 -0
- package/types/engine/site-theme-banner.d.mts +51 -0
- package/types/engine/site-theme-urls.d.mts +60 -0
- package/types/engine/site-theme.d.mts +127 -0
- package/types/engine/text-maps.d.mts +17 -3
- package/types/engine/typographer.d.mts +11 -0
- package/types/engine/verse-metre.d.mts +131 -0
- package/types/engine/wikilink-syntax.d.mts +30 -48
- package/types/engine/wikilinks.d.mts +330 -14
- package/types/engine/word-forms.d.mts +3 -2
- package/types/manifest.d.mts +3 -4
- package/types/stage.d.mts +1 -2
- package/types/vendor/markdownlint.d.mts +8 -0
- package/vendor/markdownlint.mjs +20388 -0
- package/engine/web-wikilinks.mjs +0 -594
- package/hugo-theme/layouts/404.html +0 -85
- package/hugo-theme/layouts/_default/baseof.html +0 -67
- package/hugo-theme/layouts/_default/list.html +0 -139
- package/hugo-theme/layouts/_default/single.html +0 -4
- package/hugo-theme/layouts/blog/list.html +0 -54
- package/hugo-theme/layouts/blog-post/single.html +0 -36
- package/hugo-theme/layouts/index.html +0 -75
- package/hugo-theme/layouts/partials/banner-url.html +0 -141
- package/hugo-theme/layouts/partials/cdn-url.html +0 -36
- package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
- package/hugo-theme/layouts/partials/draft-notice.html +0 -41
- package/hugo-theme/layouts/partials/draft-state.html +0 -26
- package/hugo-theme/layouts/partials/header.html +0 -35
- package/hugo-theme/layouts/partials/hero-banner.html +0 -85
- package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
- package/hugo-theme/layouts/partials/infobox/items.html +0 -26
- package/hugo-theme/layouts/partials/infobox/section.html +0 -56
- package/hugo-theme/layouts/partials/infobox/value.html +0 -43
- package/hugo-theme/layouts/partials/infobox.html +0 -57
- package/hugo-theme/layouts/partials/page-body.html +0 -152
- package/hugo-theme/layouts/partials/related.html +0 -138
- package/hugo-theme/layouts/partials/search.html +0 -84
- package/hugo-theme/layouts/partials/site-url.html +0 -32
- package/hugo-theme/layouts/partials/tags.html +0 -30
- package/hugo-theme/layouts/partials/toc.html +0 -28
- package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
- package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
- package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
- package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
- package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
- package/hugo-theme/theme.toml +0 -11
- package/types/engine/web-wikilinks.d.mts +0 -107
- /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
- /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
- /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
- /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
- /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
- /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
package/bin/package-build.mjs
CHANGED
|
@@ -91,7 +91,7 @@ import { checkForeignAssetBindings } from "../engine/asset-bindings.mjs";
|
|
|
91
91
|
import { emitDiagnostic, formatDiagnostic, positionOfYamlPath } from "../engine/diagnostics.mjs";
|
|
92
92
|
import { writeSiteRoot } from "../engine/site-root.mjs";
|
|
93
93
|
import { DEPLOY_ROOT } from "../engine/site-config.mjs";
|
|
94
|
-
import { compilesFoundryDocuments } from "../content-config.mjs";
|
|
94
|
+
import { compilesFoundryDocuments, siteBase } from "../content-config.mjs";
|
|
95
95
|
import { loadPackConfig, packConfigPath, resolveConfigFile } from "../engine/pack-config.mjs";
|
|
96
96
|
import { cleanBuildArtifacts, resetStage, stageAssets } from "../stage.mjs";
|
|
97
97
|
import { rowsStagingPictureRoots, stagePackageAssets } from "../engine/asset-shipping.mjs";
|
|
@@ -889,7 +889,7 @@ function manifestCommand() {
|
|
|
889
889
|
* `package-build site-root` — the deployment's root files, and the search
|
|
890
890
|
* index.
|
|
891
891
|
*
|
|
892
|
-
*
|
|
892
|
+
* The site render owns everything under the `/<package>/` prefix; this owns what sits
|
|
893
893
|
* beside it, which is what Cloudflare Pages reads from the uploaded directory
|
|
894
894
|
* and nowhere else: `_headers`, and no `_redirects`, since the prefix root is
|
|
895
895
|
* the homepage. It then indexes the rendered pages for search into
|
|
@@ -913,6 +913,7 @@ function siteRootCommand() {
|
|
|
913
913
|
|
|
914
914
|
const { files, search } = await writeSiteRoot({
|
|
915
915
|
pkg: shared.contentPackage,
|
|
916
|
+
base: siteBase(shared),
|
|
916
917
|
out,
|
|
917
918
|
search: shared.site.search,
|
|
918
919
|
});
|
package/ci/check-banners.mjs
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* CI guard: every banner `data/banners.yaml` declares must actually be published.
|
|
15
|
+
* CI guard: every banner `site-theme/data/banners.yaml` declares must actually be published.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
* cannot ask a remote asset host that question
|
|
19
|
-
* declared in `data/banners.yaml` instead. This checks every declaration and
|
|
17
|
+
* The hero banner needs to know which banner images exist, and the build
|
|
18
|
+
* cannot ask a remote asset host that question — so the answer is
|
|
19
|
+
* declared in `site-theme/data/banners.yaml` instead. This checks every declaration and
|
|
20
20
|
* accepts HTTP 200 or 206. HTTP 404 and 410 identify absent artwork; other HTTP
|
|
21
21
|
* statuses and transport errors leave publication unverified and fail the check.
|
|
22
22
|
*
|
|
23
|
-
* It also refuses a name in `data/banner-kinds.yaml` that `banners.yaml` does
|
|
23
|
+
* It also refuses a name in `site-theme/data/banner-kinds.yaml` that `banners.yaml` does
|
|
24
24
|
* not declare, before any request is made: a mapping to undrawn art is a dead
|
|
25
25
|
* URL on every page of that kind.
|
|
26
26
|
*
|
|
@@ -41,8 +41,8 @@ import { dirname, resolve } from "node:path";
|
|
|
41
41
|
import { parse } from "yaml";
|
|
42
42
|
|
|
43
43
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
44
|
-
const DATA_FILE = resolve(HERE, "..", "
|
|
45
|
-
const KINDS_FILE = resolve(HERE, "..", "
|
|
44
|
+
const DATA_FILE = resolve(HERE, "..", "site-theme", "data", "banners.yaml");
|
|
45
|
+
const KINDS_FILE = resolve(HERE, "..", "site-theme", "data", "banner-kinds.yaml");
|
|
46
46
|
const DEFAULT_BASE = "https://cdn.heroiclands.org";
|
|
47
47
|
|
|
48
48
|
function parseArgs(argv) {
|
package/config.mjs
CHANGED
|
@@ -160,7 +160,7 @@ const ARTIFACT_OF_KIND = Object.freeze({
|
|
|
160
160
|
*
|
|
161
161
|
* Exported so a sibling module composing a configuration value this module
|
|
162
162
|
* does not itself validate — {@link module:engine/site-config}'s
|
|
163
|
-
* `
|
|
163
|
+
* `siteSettings`, checking `site.assets` — reports through the one helper
|
|
164
164
|
* rather than a second copy.
|
|
165
165
|
*
|
|
166
166
|
* @param {string} where - Dotted path of the offending key.
|
|
@@ -657,8 +657,7 @@ export function resolveAssetTransform(declared, rootDir) {
|
|
|
657
657
|
*
|
|
658
658
|
* A package's Foundry manifest already derives its own `url` from
|
|
659
659
|
* `contentPackage` (`packageHomepage` in `manifest.mjs`); `homepage` states
|
|
660
|
-
* the same address a second time, in `package.json`, for the
|
|
661
|
-
* configuration to read a `baseURL` from without knowing where each
|
|
660
|
+
* the same address a second time, in `package.json`, for the site build to read a base URL from without knowing where each
|
|
662
661
|
* repository keeps its own site configuration.
|
|
663
662
|
*
|
|
664
663
|
* Required unconditionally: every package publishes a site, so there is no
|
|
@@ -670,28 +669,44 @@ export function resolveAssetTransform(declared, rootDir) {
|
|
|
670
669
|
* `packageHomepage(contentPackage)`, independent of it. The right caller is
|
|
671
670
|
* whatever reads `homepage` to write a site's `baseURL`.
|
|
672
671
|
*
|
|
672
|
+
* A site that states `site.base` is served at that path instead, and its
|
|
673
|
+
* `homepage` is that path on its origin: `/` makes it the origin's root.
|
|
674
|
+
*
|
|
673
675
|
* @param {string|null} homepage - The resolved `package.json` `homepage`, or
|
|
674
676
|
* `null` when none is declared.
|
|
675
677
|
* @param {string} contentPackage - The resolved `contentPackage`.
|
|
678
|
+
* @param {string} [base] - The resolved `site.base`; `""` or absent for a
|
|
679
|
+
* package site, served at `/<contentPackage>/`.
|
|
676
680
|
* @returns {void}
|
|
677
681
|
*/
|
|
678
|
-
export function checkHomepage(homepage, contentPackage) {
|
|
682
|
+
export function checkHomepage(homepage, contentPackage, base = "") {
|
|
679
683
|
if (homepage === null) {
|
|
680
684
|
fail(
|
|
681
685
|
"homepage",
|
|
682
686
|
"is not declared in `package.json`, and every package needs one " +
|
|
683
687
|
"to build its site's `baseURL` from. Add " +
|
|
684
|
-
`\`https://www.heroiclands.org
|
|
688
|
+
`\`https://www.heroiclands.org${base || `/${contentPackage}/`}\``,
|
|
685
689
|
);
|
|
686
690
|
}
|
|
687
691
|
|
|
688
|
-
const suffix = `/${contentPackage}/`;
|
|
689
692
|
let pathname;
|
|
690
693
|
try {
|
|
691
694
|
pathname = new URL(homepage).pathname;
|
|
692
695
|
} catch {
|
|
693
696
|
fail("homepage", `is \`${homepage}\`, which is not an absolute URL`);
|
|
694
697
|
}
|
|
698
|
+
if (base) {
|
|
699
|
+
if (pathname === base) return;
|
|
700
|
+
fail(
|
|
701
|
+
"homepage",
|
|
702
|
+
base === "/" ?
|
|
703
|
+
`is \`${homepage}\`, but \`site.base\` is \`/\`, so \`package.json\`'s ` +
|
|
704
|
+
"`homepage` must be the root of its origin"
|
|
705
|
+
: `is \`${homepage}\`, but \`site.base\` is \`${base}\`, so \`package.json\`'s ` +
|
|
706
|
+
`\`homepage\` must be served at \`${base}\``,
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
const suffix = `/${contentPackage}/`;
|
|
695
710
|
if (!pathname.endsWith(suffix)) {
|
|
696
711
|
fail(
|
|
697
712
|
"homepage",
|
package/content-config.mjs
CHANGED
|
@@ -215,6 +215,18 @@ export const DEFAULT_ADDRESS_SCHEME = Object.freeze({
|
|
|
215
215
|
prefix: "",
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* The path a package's site is served at: `site.base` where it states one,
|
|
220
|
+
* `/<contentPackage>/` otherwise.
|
|
221
|
+
*
|
|
222
|
+
* @param {{contentPackage: string, site: {base: string}}} config - The resolved
|
|
223
|
+
* configuration.
|
|
224
|
+
* @returns {string} The path, beginning and ending with `/`.
|
|
225
|
+
*/
|
|
226
|
+
export function siteBase(config) {
|
|
227
|
+
return config.site.base || `/${config.contentPackage}/`;
|
|
228
|
+
}
|
|
229
|
+
|
|
218
230
|
/**
|
|
219
231
|
* Whether this package publishes the pages its content tree compiles to.
|
|
220
232
|
*
|
|
@@ -347,7 +359,7 @@ export function publishesContentPages(config) {
|
|
|
347
359
|
* for *every* declared dependency, not
|
|
348
360
|
* only those supplying a catalogue.
|
|
349
361
|
* @property {string} [navigationCache] Where the site navigation is fetched
|
|
350
|
-
* to, for the
|
|
362
|
+
* to, for the site's menu.
|
|
351
363
|
*/
|
|
352
364
|
|
|
353
365
|
/**
|
|
@@ -779,9 +791,13 @@ const SITE_KEYS = [
|
|
|
779
791
|
"pass",
|
|
780
792
|
"passOptions",
|
|
781
793
|
"notfound",
|
|
782
|
-
"hugo",
|
|
783
794
|
"search",
|
|
784
795
|
"maps",
|
|
796
|
+
"sections",
|
|
797
|
+
"feed",
|
|
798
|
+
"tags",
|
|
799
|
+
"sitemaps",
|
|
800
|
+
"mergeIndexes",
|
|
785
801
|
];
|
|
786
802
|
const SITE_NOTFOUND_KEYS = ["tagline", "sitenoun", "heroimage", "links"];
|
|
787
803
|
const SITE_NOTFOUND_LINK_KEYS = ["title", "url", "text"];
|
|
@@ -1715,46 +1731,6 @@ function normalizeSiteDescription(value) {
|
|
|
1715
1731
|
return description;
|
|
1716
1732
|
}
|
|
1717
1733
|
|
|
1718
|
-
/**
|
|
1719
|
-
* Hugo keys a repository may **not** declare under `site.hugo`, because the
|
|
1720
|
-
* site build generates them and would only overwrite what was written.
|
|
1721
|
-
*
|
|
1722
|
-
* The same rule `DERIVED_MANIFEST_KEYS` states for the manifest, for the same
|
|
1723
|
-
* reason: an authored `baseURL` would look authoritative, sit there unread,
|
|
1724
|
-
* and disagree with the site forever. Each key names where its value comes
|
|
1725
|
-
* from. A dotted key names a nested one, and covers everything beneath it —
|
|
1726
|
-
* `params.brand` refuses `params.brand.logo` too — so `site.hugo` reaches only
|
|
1727
|
-
* what the generator does not write.
|
|
1728
|
-
*
|
|
1729
|
-
* @type {Readonly<Record<string, string>>}
|
|
1730
|
-
*/
|
|
1731
|
-
export const DERIVED_HUGO_KEYS = Object.freeze({
|
|
1732
|
-
baseURL: "package.json `homepage`",
|
|
1733
|
-
title: "`site.title` or `packageBuild.manifest.title`",
|
|
1734
|
-
locale: "the organisation's locale, in `engine/site-config.mjs`",
|
|
1735
|
-
publishDir: "`contentPackage`, under the deployment root `build/site`",
|
|
1736
|
-
contentDir: "the fixed content mount, `build/hugo/content`",
|
|
1737
|
-
themesDir: "where `@heroiclands/hugo-theme` is installed",
|
|
1738
|
-
theme: "the installed `@heroiclands/hugo-theme`",
|
|
1739
|
-
disableKinds:
|
|
1740
|
-
"the toolchain, which renders a site as its homepage and its pages: " +
|
|
1741
|
-
"`section`, `taxonomy`, `term` and `RSS` are disabled on every site",
|
|
1742
|
-
taxonomies:
|
|
1743
|
-
"the toolchain, which renders a site as its homepage and its pages: no " +
|
|
1744
|
-
"taxonomy is declared, because `taxonomy` and `term` are disabled kinds",
|
|
1745
|
-
outputs:
|
|
1746
|
-
"the toolchain, which renders a site as its homepage and its pages: no " +
|
|
1747
|
-
"output format is declared, because every listing kind is disabled",
|
|
1748
|
-
"params.description": "`site.description`",
|
|
1749
|
-
"params.author": "the first entry of `authors`",
|
|
1750
|
-
"params.cdnBaseURL": "`site.assets`",
|
|
1751
|
-
"params.brand": "the organisation's brand links, in `engine/site-config.mjs`",
|
|
1752
|
-
"params.notfound": "`site.notfound`",
|
|
1753
|
-
"markup.goldmark.renderer.unsafe": "the toolchain, whose pages carry raw HTML",
|
|
1754
|
-
"markup.goldmark.extensions.taskList": "the toolchain, whose format has no checkbox",
|
|
1755
|
-
menu: "the navigation `package-build deps fetch` caches from heroiclands.org",
|
|
1756
|
-
});
|
|
1757
|
-
|
|
1758
1734
|
/**
|
|
1759
1735
|
* The `site.notfound` block — the wording of the "page not found" page.
|
|
1760
1736
|
*
|
|
@@ -1799,67 +1775,177 @@ function normalizeSiteNotfound(value) {
|
|
|
1799
1775
|
}
|
|
1800
1776
|
|
|
1801
1777
|
/**
|
|
1802
|
-
* The
|
|
1803
|
-
*
|
|
1778
|
+
* The top-level paths a site writes itself, which no section may claim: the
|
|
1779
|
+
* tag pages, the theme's stylesheets, scripts, fonts and images, and the
|
|
1780
|
+
* search index.
|
|
1804
1781
|
*
|
|
1805
|
-
*
|
|
1806
|
-
|
|
1807
|
-
|
|
1782
|
+
* @type {readonly string[]}
|
|
1783
|
+
*/
|
|
1784
|
+
const SITE_WRITTEN_SEGMENTS = Object.freeze(["tags", "css", "js", "fonts", "img", "pagefind"]);
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* A note's address as a section names its page: `<type>-<shortcode>`, the
|
|
1788
|
+
* form a page is published at.
|
|
1789
|
+
*/
|
|
1790
|
+
const PAGE_ADDRESS = /^[a-z][a-z0-9]*-[a-z0-9]+$/;
|
|
1791
|
+
|
|
1792
|
+
/**
|
|
1793
|
+
* Where a site is served, as `site.base` states it: a path that begins and
|
|
1794
|
+
* ends with `/`. `/` is the root of the origin.
|
|
1795
|
+
*
|
|
1796
|
+
* @param {unknown} value - The configured value, or `undefined`.
|
|
1797
|
+
* @returns {string} The path; `""` when unset, which is `/<contentPackage>/`.
|
|
1798
|
+
*/
|
|
1799
|
+
function normalizeSiteBase(value) {
|
|
1800
|
+
if (value === undefined) return "";
|
|
1801
|
+
const base = requireNonEmptyString(value, "site.base");
|
|
1802
|
+
if (!base.startsWith("/") || !base.endsWith("/")) {
|
|
1803
|
+
fail(
|
|
1804
|
+
"site.base",
|
|
1805
|
+
`is \`${base}\`; it is the path the site is served at, which begins and ends with \`/\` ` +
|
|
1806
|
+
"— `/` for the root of the origin",
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
return base;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
/**
|
|
1813
|
+
* One address segment naming a section, as `site.sections` keys it and
|
|
1814
|
+
* `site.feed` names it.
|
|
1815
|
+
*
|
|
1816
|
+
* @param {string} name - The section's name.
|
|
1817
|
+
* @param {string} field - Dotted path, for the error.
|
|
1818
|
+
* @returns {string} The name.
|
|
1819
|
+
*/
|
|
1820
|
+
function sectionName(name, field) {
|
|
1821
|
+
if (!isAddressSegment(name)) {
|
|
1822
|
+
fail(
|
|
1823
|
+
field,
|
|
1824
|
+
`names the section \`${name}\`, which is published at \`/${name}/\` and so is one ` +
|
|
1825
|
+
"address segment: lowercase letters and digits",
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
if (SITE_WRITTEN_SEGMENTS.includes(name)) {
|
|
1829
|
+
fail(
|
|
1830
|
+
field,
|
|
1831
|
+
`names the section \`${name}\`, a path the site writes itself ` +
|
|
1832
|
+
`(${SITE_WRITTEN_SEGMENTS.join(", ")})`,
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
return name;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/**
|
|
1839
|
+
* The `site.sections` mapping: each section's name, and the address of the
|
|
1840
|
+
* note that is its page.
|
|
1808
1841
|
*
|
|
1809
1842
|
* @param {unknown} value - The block, or `undefined`.
|
|
1810
|
-
* @returns {Readonly<Record<string,
|
|
1843
|
+
* @returns {Readonly<Record<string, string>>} It, frozen; `{}` when absent.
|
|
1811
1844
|
*/
|
|
1812
|
-
function
|
|
1845
|
+
function normalizeSiteSections(value) {
|
|
1813
1846
|
if (value === undefined) return Object.freeze({});
|
|
1814
|
-
if (!isPlainObject(value))
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1847
|
+
if (!isPlainObject(value)) {
|
|
1848
|
+
fail(
|
|
1849
|
+
"site.sections",
|
|
1850
|
+
"must be a mapping of a section's name to the address of the note that is its page",
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
/** @type {Record<string, string>} */
|
|
1854
|
+
const out = {};
|
|
1855
|
+
/** @type {Map<string, string>} */
|
|
1856
|
+
const claimed = new Map();
|
|
1857
|
+
for (const [name, page] of Object.entries(/** @type {object} */ (value))) {
|
|
1858
|
+
const field = `site.sections.${name}`;
|
|
1859
|
+
sectionName(name, field);
|
|
1860
|
+
if (typeof page !== "string" || !PAGE_ADDRESS.test(page)) {
|
|
1861
|
+
fail(
|
|
1862
|
+
field,
|
|
1863
|
+
`is \`${String(page)}\`; it is the address of the note that is the section's ` +
|
|
1864
|
+
"page, written `<type>-<shortcode>`, such as `doc-blog`",
|
|
1865
|
+
);
|
|
1822
1866
|
}
|
|
1823
|
-
if (
|
|
1867
|
+
if (page.startsWith("homepage-")) {
|
|
1824
1868
|
fail(
|
|
1825
|
-
|
|
1826
|
-
`is
|
|
1827
|
-
`would be overwritten, and the two would disagree with ` +
|
|
1828
|
-
`nothing to say so`,
|
|
1869
|
+
field,
|
|
1870
|
+
`is \`${page}\`, and the homepage is the site's front page, not a section's`,
|
|
1829
1871
|
);
|
|
1830
1872
|
}
|
|
1873
|
+
if (claimed.has(page)) {
|
|
1874
|
+
fail(field, `is \`${page}\`, which is already the page of \`${claimed.get(page)}\``);
|
|
1875
|
+
}
|
|
1876
|
+
claimed.set(page, name);
|
|
1877
|
+
out[name] = page;
|
|
1878
|
+
}
|
|
1879
|
+
return Object.freeze(out);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* A list of package sites, by `contentPackage`, as `site.sitemaps` and
|
|
1884
|
+
* `site.mergeIndexes` name them.
|
|
1885
|
+
*
|
|
1886
|
+
* @param {unknown} value - The list, or `undefined`.
|
|
1887
|
+
* @param {string} field - Dotted path, for the error.
|
|
1888
|
+
* @param {string} contentPackage - This site's own package, which no entry names.
|
|
1889
|
+
* @returns {readonly string[]} The names, frozen; `[]` when absent.
|
|
1890
|
+
*/
|
|
1891
|
+
function packageSiteList(value, field, contentPackage) {
|
|
1892
|
+
if (value === undefined) return Object.freeze([]);
|
|
1893
|
+
if (!Array.isArray(value)) {
|
|
1894
|
+
fail(field, "must be a list of package sites, each named by its `contentPackage`");
|
|
1831
1895
|
}
|
|
1832
|
-
|
|
1896
|
+
const seen = new Set();
|
|
1897
|
+
return Object.freeze(
|
|
1898
|
+
value.map((entry, i) => {
|
|
1899
|
+
const where = `${field}[${i}]`;
|
|
1900
|
+
if (!isAddressSegment(entry)) {
|
|
1901
|
+
fail(
|
|
1902
|
+
where,
|
|
1903
|
+
`is \`${String(entry)}\`, which is not a package site: name one by its ` +
|
|
1904
|
+
"`contentPackage`, lowercase letters and digits",
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
if (entry === contentPackage) {
|
|
1908
|
+
fail(where, `is \`${entry}\`, this site's own package`);
|
|
1909
|
+
}
|
|
1910
|
+
if (seen.has(entry)) fail(where, `names \`${entry}\` more than once`);
|
|
1911
|
+
seen.add(entry);
|
|
1912
|
+
return /** @type {string} */ (entry);
|
|
1913
|
+
}),
|
|
1914
|
+
);
|
|
1833
1915
|
}
|
|
1834
1916
|
|
|
1835
1917
|
/**
|
|
1836
1918
|
* The `site` section — how this repository frames the website it publishes.
|
|
1837
1919
|
*
|
|
1838
1920
|
* Everything here is *framing*: which named pass bundle supplies the
|
|
1839
|
-
* repository's own body rewrites, and the residue of the
|
|
1840
|
-
*
|
|
1841
|
-
* is
|
|
1842
|
-
* `build/hugo/`. How a page gets its
|
|
1843
|
-
* **address** is deliberately not here either — that is `publish.address`,
|
|
1921
|
+
* repository's own body rewrites, and the residue of the site that is
|
|
1922
|
+
* genuinely this repository's own. How a page gets its
|
|
1923
|
+
* **address** is deliberately not here — that is `publish.address`,
|
|
1844
1924
|
* shared with the link manifest so the two cannot disagree about where a
|
|
1845
1925
|
* page is.
|
|
1846
1926
|
*
|
|
1847
1927
|
* Two switches sit here, both on by default. `search` is whether
|
|
1848
1928
|
* `package-build site-root` writes a search index beside the rendered pages.
|
|
1849
|
-
* `maps` is whether `package-build site` draws the map from each place that
|
|
1929
|
+
* `maps` is whether `package-build site build` draws the map from each place that
|
|
1850
1930
|
* states a border or a route and writes it into that place's page — a site
|
|
1851
1931
|
* carries both unless it says otherwise.
|
|
1852
1932
|
*
|
|
1853
|
-
* **
|
|
1854
|
-
*
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
1857
|
-
*
|
|
1933
|
+
* **A site is its homepage, its pages and the listings it asks for.** A page
|
|
1934
|
+
* of documentation is a note — `type: doc`, addressed by its shortcode,
|
|
1935
|
+
* compiling into no document where it says `pack: none` — so there is no
|
|
1936
|
+
* second mechanism for mounting a directory of markdown. `sections` publishes
|
|
1937
|
+
* a note at `/<section>/` as that section's page; `feed` names the section a
|
|
1938
|
+
* site's posts are listed and fed in; `tags` writes a page for each tag.
|
|
1939
|
+
* `base` is where the site is served, and a site at the root of its origin
|
|
1940
|
+
* (`/`) may name the package sites beside it: `sitemaps` for its sitemap
|
|
1941
|
+
* index and `robots.txt`, `mergeIndexes` for the search indexes its own
|
|
1942
|
+
* search reads.
|
|
1858
1943
|
*
|
|
1859
1944
|
* @param {unknown} value - The `site` block, or `undefined`.
|
|
1945
|
+
* @param {string} contentPackage - This repository's package.
|
|
1860
1946
|
* @returns {Readonly<object>} It, frozen, with every default filled.
|
|
1861
1947
|
*/
|
|
1862
|
-
function normalizeSite(value) {
|
|
1948
|
+
function normalizeSite(value, contentPackage) {
|
|
1863
1949
|
const empty = Object.freeze({
|
|
1864
1950
|
base: "",
|
|
1865
1951
|
title: "",
|
|
@@ -1869,9 +1955,13 @@ function normalizeSite(value) {
|
|
|
1869
1955
|
pass: "",
|
|
1870
1956
|
passOptions: Object.freeze({}),
|
|
1871
1957
|
notfound: null,
|
|
1872
|
-
hugo: Object.freeze({}),
|
|
1873
1958
|
search: true,
|
|
1874
1959
|
maps: true,
|
|
1960
|
+
sections: Object.freeze({}),
|
|
1961
|
+
feed: "",
|
|
1962
|
+
tags: false,
|
|
1963
|
+
sitemaps: Object.freeze([]),
|
|
1964
|
+
mergeIndexes: Object.freeze([]),
|
|
1875
1965
|
});
|
|
1876
1966
|
if (value === undefined) return empty;
|
|
1877
1967
|
if (!isPlainObject(value)) fail("site", "must be a mapping");
|
|
@@ -1886,8 +1976,28 @@ function normalizeSite(value) {
|
|
|
1886
1976
|
packages = input.packages.map((p, i) => requireNonEmptyString(p, `site.packages[${i}]`));
|
|
1887
1977
|
}
|
|
1888
1978
|
|
|
1979
|
+
const base = normalizeSiteBase(input.base);
|
|
1980
|
+
const search = optionalBoolean(input.search, "site.search", true);
|
|
1981
|
+
const sitemaps = packageSiteList(input.sitemaps, "site.sitemaps", contentPackage);
|
|
1982
|
+
if (sitemaps.length && base !== "/") {
|
|
1983
|
+
fail(
|
|
1984
|
+
"site.sitemaps",
|
|
1985
|
+
"names the package sites a sitemap index lists, and a sitemap index and " +
|
|
1986
|
+
"`robots.txt` belong at the root of an origin; this site is served at " +
|
|
1987
|
+
`\`${base || `/${contentPackage}/`}\`, so declare \`site.base: /\` or drop the key`,
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
const mergeIndexes = packageSiteList(input.mergeIndexes, "site.mergeIndexes", contentPackage);
|
|
1991
|
+
if (mergeIndexes.length && !search) {
|
|
1992
|
+
fail(
|
|
1993
|
+
"site.mergeIndexes",
|
|
1994
|
+
"names the search indexes this site's search reads beside its own, and " +
|
|
1995
|
+
"`site.search` is `false`, so there is no search to read them",
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1889
1999
|
return Object.freeze({
|
|
1890
|
-
base
|
|
2000
|
+
base,
|
|
1891
2001
|
title: input.title === undefined ? "" : requireNonEmptyString(input.title, "site.title"),
|
|
1892
2002
|
assets: normalizeSiteAssets(input.assets),
|
|
1893
2003
|
description: normalizeSiteDescription(input.description),
|
|
@@ -1898,9 +2008,16 @@ function normalizeSite(value) {
|
|
|
1898
2008
|
Object.freeze({})
|
|
1899
2009
|
: Object.freeze({ ...input.passOptions }),
|
|
1900
2010
|
notfound: normalizeSiteNotfound(input.notfound),
|
|
1901
|
-
|
|
1902
|
-
search: optionalBoolean(input.search, "site.search", true),
|
|
2011
|
+
search,
|
|
1903
2012
|
maps: optionalBoolean(input.maps, "site.maps", true),
|
|
2013
|
+
sections: normalizeSiteSections(input.sections),
|
|
2014
|
+
feed:
|
|
2015
|
+
input.feed === undefined ?
|
|
2016
|
+
""
|
|
2017
|
+
: sectionName(requireNonEmptyString(input.feed, "site.feed"), "site.feed"),
|
|
2018
|
+
tags: optionalBoolean(input.tags, "site.tags", false),
|
|
2019
|
+
sitemaps,
|
|
2020
|
+
mergeIndexes,
|
|
1904
2021
|
});
|
|
1905
2022
|
}
|
|
1906
2023
|
|
|
@@ -3150,7 +3267,9 @@ export function defineConfig(config) {
|
|
|
3150
3267
|
packs: Object.freeze(packs),
|
|
3151
3268
|
packDirectories: Object.freeze(packDirectories),
|
|
3152
3269
|
docs: normalizeDocs(input.docs),
|
|
3153
|
-
site
|
|
3270
|
+
// Validated above, in `contentPackage`: a site names the package sites
|
|
3271
|
+
// beside it, and never itself.
|
|
3272
|
+
site: normalizeSite(input.site, /** @type {string} */ (input.contentPackage)),
|
|
3154
3273
|
// Validated above, in `contentPackage`: a book's default file stem is
|
|
3155
3274
|
// `<contentPackage>-<id>`.
|
|
3156
3275
|
pdf: normalizePdf(input.pdf, rootDir, /** @type {string} */ (input.contentPackage)),
|