@heroiclands/package-build 23.13.0 → 24.0.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 +50 -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 +248 -80
- 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 +155 -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 +265 -313
- 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-reach.mjs +213 -0
- package/engine/site-render.mjs +668 -0
- package/engine/site-root.mjs +90 -39
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +274 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +939 -0
- package/engine/sql-tables.mjs +9 -6
- 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-reach.d.mts +76 -0
- package/types/engine/site-render.d.mts +173 -0
- package/types/engine/site-root.d.mts +14 -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
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **A site, written as HTML.** {@link writeSite} draws every page of a site
|
|
16
|
+
* through the layouts of {@link module:engine/site-theme} and writes the files
|
|
17
|
+
* that are deployed: one `index.html` per page, `404.html`, `sitemap.xml`, the
|
|
18
|
+
* stylesheets, scripts and fonts the layouts link, and, where the site asks
|
|
19
|
+
* for them, its listings, its feed, a sitemap index and `robots.txt`.
|
|
20
|
+
* {@link packageSite} turns a package's site build into the pages
|
|
21
|
+
* `writeSite` is given.
|
|
22
|
+
*
|
|
23
|
+
* ## The pages of a site
|
|
24
|
+
*
|
|
25
|
+
* A site is given its home page, its regular pages and any section pages its
|
|
26
|
+
* author wrote. The rest is derived:
|
|
27
|
+
*
|
|
28
|
+
* | Page | Where | Lists |
|
|
29
|
+
* | ---- | ----- | ----- |
|
|
30
|
+
* | a section | `<section>/` | the section's pages |
|
|
31
|
+
* | the tags | `tags/` | every tag, with how many pages carry it |
|
|
32
|
+
* | a tag | `tags/<tag>/` | the pages that carry it |
|
|
33
|
+
*
|
|
34
|
+
* A section is the `section` a regular page names; a page that names none is
|
|
35
|
+
* in no section. A section page an author wrote keeps its title and its body,
|
|
36
|
+
* and lists the section's pages the body does not already reach. The tag pages
|
|
37
|
+
* are written only with `tagPages`, and a page's tag links to its page only
|
|
38
|
+
* then.
|
|
39
|
+
*
|
|
40
|
+
* The pages of a section are in the order a reader walks them, which their
|
|
41
|
+
* previous and next links follow: by `weight`, lightest first and unweighted
|
|
42
|
+
* last, then newest first by `date`, then by title in English order, then by
|
|
43
|
+
* address.
|
|
44
|
+
*
|
|
45
|
+
* ## The files beside the pages
|
|
46
|
+
*
|
|
47
|
+
* | Site states | Written |
|
|
48
|
+
* | ----------- | ------- |
|
|
49
|
+
* | always | `404.html`, `sitemap.xml`, `css/code.css` and the theme's own files |
|
|
50
|
+
* | `feed: {section, title}` | `<section>/atom.xml`, the section's pages, newest first |
|
|
51
|
+
* | `sitemaps: [...]` | `sitemap-index.xml`, naming this site's sitemap and each one listed, and `robots.txt` naming them all |
|
|
52
|
+
*
|
|
53
|
+
* Every page is written, but `sitemap.xml` lists only the pages reachable from
|
|
54
|
+
* the homepage by the links the rendered pages carry; see
|
|
55
|
+
* {@link module:engine/site-reach}. A feed is a link, so the pages it lists
|
|
56
|
+
* are reachable when a page links the feed, and a section or tag page lists
|
|
57
|
+
* pages it links to the same way.
|
|
58
|
+
*
|
|
59
|
+
* A sitemap index and `robots.txt` belong at the root of an origin, so a site
|
|
60
|
+
* served under a path refuses `sitemaps`.
|
|
61
|
+
*
|
|
62
|
+
* Every page is minified as it is written; see
|
|
63
|
+
* {@link module:engine/html-minify}.
|
|
64
|
+
*
|
|
65
|
+
* @module
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import fs from "node:fs";
|
|
69
|
+
import path from "node:path";
|
|
70
|
+
import { fileURLToPath } from "node:url";
|
|
71
|
+
|
|
72
|
+
import { createRenderer } from "./content-render.mjs";
|
|
73
|
+
import { resolvePathname } from "./pathnames.mjs";
|
|
74
|
+
import { minifyHtml } from "./html-minify.mjs";
|
|
75
|
+
import { mapWorld } from "./map-places.mjs";
|
|
76
|
+
import {
|
|
77
|
+
checkScriptFonts,
|
|
78
|
+
copyScriptFonts,
|
|
79
|
+
scriptFontFiles,
|
|
80
|
+
siteFontsDestination,
|
|
81
|
+
} from "./script-font-delivery.mjs";
|
|
82
|
+
import { buildSite, gatesFailed } from "./site-build.mjs";
|
|
83
|
+
import { readCachedNavigation, siteSettings } from "./site-config.mjs";
|
|
84
|
+
import { atomFeed, robotsTxt, sitemapIndexXml, sitemapXml } from "./site-files.mjs";
|
|
85
|
+
import { pageAddress, pruneGenerated, reachablePages, splitContent } from "./site-reach.mjs";
|
|
86
|
+
import { prepareTreeSqlTables } from "./sql-tables.mjs";
|
|
87
|
+
import { placeGeography, searchMetadata } from "./site-search.mjs";
|
|
88
|
+
import {
|
|
89
|
+
createSiteTheme,
|
|
90
|
+
humanize,
|
|
91
|
+
plainText,
|
|
92
|
+
siteThemeAssets,
|
|
93
|
+
subjectTags,
|
|
94
|
+
} from "./site-theme.mjs";
|
|
95
|
+
import { basePath, relUrl, tagUrl } from "./site-theme-urls.mjs";
|
|
96
|
+
import { slugify } from "./content-slug.mjs";
|
|
97
|
+
import { isPost } from "./post-fields.mjs";
|
|
98
|
+
import { siteBase } from "../content-config.mjs";
|
|
99
|
+
|
|
100
|
+
/** This package's root. */
|
|
101
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The files the layouts link that the theme ships as they are: the site and
|
|
105
|
+
* icon stylesheets, the spoiler script, the icon font and the theme's images.
|
|
106
|
+
*/
|
|
107
|
+
export const SITE_STATIC_DIR = path.join(PACKAGE_ROOT, "site-theme", "static");
|
|
108
|
+
|
|
109
|
+
/** The words of a post a feed entry summarises it with. */
|
|
110
|
+
const SUMMARY_WORDS = 70;
|
|
111
|
+
|
|
112
|
+
/** @param {unknown} value */
|
|
113
|
+
const text = (value) => (value === undefined || value === null ? "" : String(value));
|
|
114
|
+
|
|
115
|
+
/** Titles in the order an English reader sorts them. */
|
|
116
|
+
const collator = new Intl.Collator("en", { sensitivity: "variant" });
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A title as it is sorted: an apostrophe typed as `'` sorts before one typed
|
|
120
|
+
* as `’`, rather than level with it. It is read as U+205E, the punctuation
|
|
121
|
+
* mark the collation places just before both.
|
|
122
|
+
*/
|
|
123
|
+
const sortKey = (title) => text(title).replaceAll("'", "\u205e");
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Pages in the order a reader walks them: by `weight`, lightest first and an
|
|
127
|
+
* unweighted page last; then newest first by `date`; then by title; then by
|
|
128
|
+
* address. A page's own `weight` and `date` are read before its `params`'.
|
|
129
|
+
*
|
|
130
|
+
* @template {{title?: string, url: string, date?: unknown, weight?: number, params?: object}} T
|
|
131
|
+
* @param {readonly T[]} pages - The pages.
|
|
132
|
+
* @returns {T[]} A sorted copy.
|
|
133
|
+
*/
|
|
134
|
+
export function siteOrder(pages) {
|
|
135
|
+
const weight = (page) => {
|
|
136
|
+
const value = Number(page.weight ?? page.params?.weight);
|
|
137
|
+
return value > 0 ? value : Infinity;
|
|
138
|
+
};
|
|
139
|
+
const time = (page) => {
|
|
140
|
+
const value = page.date ?? page.params?.date;
|
|
141
|
+
const date = value ? new Date(String(value)).getTime() : 0;
|
|
142
|
+
return Number.isNaN(date) ? 0 : date;
|
|
143
|
+
};
|
|
144
|
+
return [...pages].sort((a, b) => {
|
|
145
|
+
if (weight(a) !== weight(b)) return weight(a) < weight(b) ? -1 : 1;
|
|
146
|
+
if (time(a) !== time(b)) return time(b) - time(a);
|
|
147
|
+
const titles = collator.compare(sortKey(a.title), sortKey(b.title));
|
|
148
|
+
if (titles) return titles;
|
|
149
|
+
return (
|
|
150
|
+
a.url < b.url ? -1
|
|
151
|
+
: a.url > b.url ? 1
|
|
152
|
+
: 0
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A page as a sibling names it. */
|
|
158
|
+
const siblingOf = (page) => ({
|
|
159
|
+
url: page.url,
|
|
160
|
+
title: page.title,
|
|
161
|
+
type: page.params?.type,
|
|
162
|
+
subType: page.params?.subType,
|
|
163
|
+
category: page.params?.category,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/** A page as a listing names it. */
|
|
167
|
+
const memberOf = (page) => ({
|
|
168
|
+
url: page.url,
|
|
169
|
+
title: page.title,
|
|
170
|
+
description: page.params?.description,
|
|
171
|
+
content: page.content,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Every page of a site: the pages it is given, with their siblings, and the
|
|
176
|
+
* listings derived from them.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} site - The site, as {@link writeSite} takes it.
|
|
179
|
+
* @param {readonly object[]} pages - The home page, the regular pages and any
|
|
180
|
+
* authored section pages.
|
|
181
|
+
* @returns {object[]} Every page, home first, then the regular pages, the
|
|
182
|
+
* sections, and the tag pages.
|
|
183
|
+
*/
|
|
184
|
+
export function siteTree(site, pages) {
|
|
185
|
+
const home = pages.filter((page) => page.kind === "home");
|
|
186
|
+
const regular = pages.filter((page) => page.kind === "page");
|
|
187
|
+
const authored = new Map(
|
|
188
|
+
pages.filter((page) => page.kind === "section").map((page) => [page.section, page]),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
/** @type {Map<string, object[]>} */
|
|
192
|
+
const sections = new Map();
|
|
193
|
+
for (const page of regular) {
|
|
194
|
+
const key = text(page.section);
|
|
195
|
+
if (!sections.has(key)) sections.set(key, []);
|
|
196
|
+
sections.get(key).push(page);
|
|
197
|
+
}
|
|
198
|
+
const listings = [];
|
|
199
|
+
for (const [key, list] of sections) {
|
|
200
|
+
const ordered = siteOrder(list);
|
|
201
|
+
const siblings = ordered.map(siblingOf);
|
|
202
|
+
for (const page of ordered) page.siblings = siblings;
|
|
203
|
+
if (!key) continue;
|
|
204
|
+
const page = authored.get(key) ?? {
|
|
205
|
+
kind: "section",
|
|
206
|
+
section: key,
|
|
207
|
+
title: humanize(key),
|
|
208
|
+
url: relUrl(site, `${key}/`),
|
|
209
|
+
params: {},
|
|
210
|
+
content: "",
|
|
211
|
+
};
|
|
212
|
+
listings.push({ ...page, members: ordered.map(memberOf) });
|
|
213
|
+
}
|
|
214
|
+
for (const [key, page] of authored)
|
|
215
|
+
if (!sections.has(key)) listings.push({ ...page, members: [] });
|
|
216
|
+
|
|
217
|
+
if (!site.tagPages) return [...home, ...regular, ...listings];
|
|
218
|
+
|
|
219
|
+
/** @type {Map<string, {name: string, pages: object[]}>} */
|
|
220
|
+
const tags = new Map();
|
|
221
|
+
for (const page of regular)
|
|
222
|
+
for (const name of subjectTags(page.params)) {
|
|
223
|
+
const key = slugify(name);
|
|
224
|
+
if (!key) continue;
|
|
225
|
+
if (!tags.has(key)) tags.set(key, { name, pages: [] });
|
|
226
|
+
tags.get(key).pages.push(page);
|
|
227
|
+
}
|
|
228
|
+
const terms = [...tags.values()].map((tag) => ({
|
|
229
|
+
kind: "term",
|
|
230
|
+
title: tag.name,
|
|
231
|
+
url: tagUrl(site, tag.name),
|
|
232
|
+
params: {},
|
|
233
|
+
content: "",
|
|
234
|
+
members: siteOrder(tag.pages).map(memberOf),
|
|
235
|
+
count: tag.pages.length,
|
|
236
|
+
date: tag.pages
|
|
237
|
+
.map((page) => text(page.date))
|
|
238
|
+
.filter(Boolean)
|
|
239
|
+
.sort()
|
|
240
|
+
.at(-1),
|
|
241
|
+
}));
|
|
242
|
+
const taxonomy = {
|
|
243
|
+
kind: "taxonomy",
|
|
244
|
+
title: "Tags",
|
|
245
|
+
url: relUrl(site, "tags/"),
|
|
246
|
+
params: {},
|
|
247
|
+
content: "",
|
|
248
|
+
members: siteOrder(terms).map((term) => ({
|
|
249
|
+
url: term.url,
|
|
250
|
+
title: term.title,
|
|
251
|
+
count: term.count,
|
|
252
|
+
})),
|
|
253
|
+
};
|
|
254
|
+
return [...home, ...regular, ...listings, taxonomy, ...terms];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The file a page is written to.
|
|
259
|
+
*
|
|
260
|
+
* @param {object} site - The site.
|
|
261
|
+
* @param {string} out - The directory the site is written to.
|
|
262
|
+
* @param {string} url - The page's address, under the site's base path.
|
|
263
|
+
* @returns {string} The `index.html` the address serves.
|
|
264
|
+
* @throws {Error} When the address is not under the site's base path.
|
|
265
|
+
*/
|
|
266
|
+
function pageFile(site, out, url) {
|
|
267
|
+
const base = basePath(site);
|
|
268
|
+
if (!url.startsWith(base))
|
|
269
|
+
throw new Error(`the page ${url} is not under the site's base path ${base}`);
|
|
270
|
+
return path.join(out, ...url.slice(base.length).split("/").filter(Boolean), "index.html");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Write the files the layouts link: the theme's own, and the code stylesheet.
|
|
275
|
+
*
|
|
276
|
+
* @param {string} out - The directory the site is written to.
|
|
277
|
+
* @param {string} [staticDir] - The theme's own files.
|
|
278
|
+
* @returns {string[]} The files the theme generates, each written. The theme's
|
|
279
|
+
* own files are copied as a tree and not listed.
|
|
280
|
+
*/
|
|
281
|
+
export function writeThemeFiles(out, staticDir = SITE_STATIC_DIR) {
|
|
282
|
+
fs.mkdirSync(out, { recursive: true });
|
|
283
|
+
if (fs.existsSync(staticDir)) fs.cpSync(staticDir, out, { recursive: true });
|
|
284
|
+
return siteThemeAssets().map((asset) => {
|
|
285
|
+
const file = path.join(out, asset.path);
|
|
286
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
287
|
+
fs.writeFileSync(file, asset.content);
|
|
288
|
+
return file;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Draw a site and write it.
|
|
294
|
+
*
|
|
295
|
+
* @param {object} options - The inputs.
|
|
296
|
+
* @param {object} options.site - The site, as
|
|
297
|
+
* {@link module:engine/site-theme.createSiteTheme} takes it, with `tagPages`,
|
|
298
|
+
* `feed` and `sitemaps` as the module description sets out. `pages` and
|
|
299
|
+
* `pageExists` are filled in here.
|
|
300
|
+
* @param {readonly object[]} options.pages - The home page, the regular pages
|
|
301
|
+
* and any authored section pages, each as a layout is given it.
|
|
302
|
+
* @param {string} options.out - The directory written to. Emptied first.
|
|
303
|
+
* @param {(finding: import("./site-theme.mjs").Finding) => void} [options.report] -
|
|
304
|
+
* Receives each finding the layouts make.
|
|
305
|
+
* @param {string} [options.staticDir] - The theme's own files, copied first.
|
|
306
|
+
* @param {ReadonlySet<string>} [options.only] - The addresses of the pages to
|
|
307
|
+
* draw again into a site already written. Nothing else is touched.
|
|
308
|
+
* @returns {{files: string[], pages: string[], reachable: string[],
|
|
309
|
+
* findings: object[]}} Every file written, the address of every page, the
|
|
310
|
+
* address of each page reachable from the homepage, and the layouts' findings.
|
|
311
|
+
* @throws {Error} When a page's address is outside the site, or a site served
|
|
312
|
+
* under a path asks for a sitemap index.
|
|
313
|
+
*/
|
|
314
|
+
export function writeSite({ site, pages, out, report, staticDir = SITE_STATIC_DIR, only }) {
|
|
315
|
+
if (site.sitemaps && basePath(site) !== "/")
|
|
316
|
+
throw new Error(
|
|
317
|
+
`a sitemap index and robots.txt belong at the root of an origin, and this site is served under ${basePath(site)}`,
|
|
318
|
+
);
|
|
319
|
+
const tree = siteTree(site, pages);
|
|
320
|
+
const published = new Set(tree.map((page) => page.url));
|
|
321
|
+
const feedUrl = site.feed ? relUrl(site, `${site.feed.section}/atom.xml`) : "";
|
|
322
|
+
const base = basePath(site);
|
|
323
|
+
const origin = (url) => new URL(url, site.baseURL).href;
|
|
324
|
+
|
|
325
|
+
// The address of the page a URL names, or undefined where it names none of
|
|
326
|
+
// this site's.
|
|
327
|
+
const pageOfUrl = (url) => {
|
|
328
|
+
let pathname;
|
|
329
|
+
try {
|
|
330
|
+
const resolved = new URL(String(url), origin(base));
|
|
331
|
+
if (resolved.origin !== new URL(site.baseURL).origin) return undefined;
|
|
332
|
+
pathname = decodeURI(resolved.pathname);
|
|
333
|
+
} catch {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
const address = pageAddress(pathname);
|
|
337
|
+
return published.has(address) ? address : undefined;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// A page is drawn twice. The first draw has no generated link to a page,
|
|
341
|
+
// and the pages it links are the reachable ones; the second draws the
|
|
342
|
+
// generated sections again with the links that stay inside that set.
|
|
343
|
+
const makeTheme = ({ visible, contentOf, report: sink }) =>
|
|
344
|
+
createSiteTheme({
|
|
345
|
+
site: {
|
|
346
|
+
...site,
|
|
347
|
+
visible,
|
|
348
|
+
feed:
|
|
349
|
+
site.feed ?
|
|
350
|
+
{
|
|
351
|
+
title: site.feed.title ?? site.title,
|
|
352
|
+
url: feedUrl,
|
|
353
|
+
section: text(site.feed.section),
|
|
354
|
+
}
|
|
355
|
+
: undefined,
|
|
356
|
+
pages: tree
|
|
357
|
+
.filter((page) => page.kind === "page")
|
|
358
|
+
.map((page) => ({
|
|
359
|
+
url: page.url,
|
|
360
|
+
title: page.title,
|
|
361
|
+
section: text(page.section),
|
|
362
|
+
type: page.params?.type,
|
|
363
|
+
subType: page.params?.subType,
|
|
364
|
+
date: page.date ?? page.params?.date,
|
|
365
|
+
content: contentOf(page),
|
|
366
|
+
})),
|
|
367
|
+
pageExists: (url) =>
|
|
368
|
+
published.has(url) || published.has(`${url.replace(/\/?$/, "/")}`),
|
|
369
|
+
},
|
|
370
|
+
report: sink,
|
|
371
|
+
});
|
|
372
|
+
const authoredOf = (page) => splitContent(page.content).authored;
|
|
373
|
+
const first = makeTheme({
|
|
374
|
+
visible: (url) => pageOfUrl(url) === undefined,
|
|
375
|
+
contentOf: authoredOf,
|
|
376
|
+
});
|
|
377
|
+
const firstPass = new Map();
|
|
378
|
+
for (const page of tree)
|
|
379
|
+
firstPass.set(
|
|
380
|
+
page.url,
|
|
381
|
+
first.render({
|
|
382
|
+
...page,
|
|
383
|
+
content: page.content === undefined ? undefined : authoredOf(page),
|
|
384
|
+
permalink: origin(page.url),
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const section = site.feed ? text(site.feed.section) : "";
|
|
389
|
+
const time = (page) => new Date(text(page.date ?? page.params?.date)).getTime() || 0;
|
|
390
|
+
const posts =
|
|
391
|
+
site.feed ?
|
|
392
|
+
tree
|
|
393
|
+
.filter((page) => page.kind === "page" && text(page.section) === section)
|
|
394
|
+
.sort((a, b) => time(b) - time(a))
|
|
395
|
+
: [];
|
|
396
|
+
const reachable = reachablePages({
|
|
397
|
+
base,
|
|
398
|
+
html: (url) => firstPass.get(url),
|
|
399
|
+
expand: (target) => (site.feed && target === feedUrl ? posts.map((post) => post.url) : []),
|
|
400
|
+
});
|
|
401
|
+
const outside = (url) => {
|
|
402
|
+
const address = pageOfUrl(url);
|
|
403
|
+
return address !== undefined && !reachable.has(address);
|
|
404
|
+
};
|
|
405
|
+
const contentOf = (page) => {
|
|
406
|
+
if (page.content === undefined) return page.content;
|
|
407
|
+
const { authored, generated } = splitContent(page.content);
|
|
408
|
+
return authored + pruneGenerated(generated, outside);
|
|
409
|
+
};
|
|
410
|
+
const theme = makeTheme({ visible: (url) => !outside(url), contentOf, report });
|
|
411
|
+
|
|
412
|
+
const files = [];
|
|
413
|
+
const write = (file, content) => {
|
|
414
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
415
|
+
fs.writeFileSync(file, content);
|
|
416
|
+
files.push(file);
|
|
417
|
+
};
|
|
418
|
+
const draw = (page) => {
|
|
419
|
+
const html = minifyHtml(
|
|
420
|
+
theme.render({ ...page, content: contentOf(page), permalink: origin(page.url) }),
|
|
421
|
+
);
|
|
422
|
+
write(pageFile(site, out, page.url), html);
|
|
423
|
+
};
|
|
424
|
+
const result = (extra = {}) => ({
|
|
425
|
+
files,
|
|
426
|
+
pages: tree.map((page) => page.url),
|
|
427
|
+
reachable: [...reachable],
|
|
428
|
+
findings: theme.findings,
|
|
429
|
+
...extra,
|
|
430
|
+
});
|
|
431
|
+
if (only) {
|
|
432
|
+
for (const page of tree) if (only.has(page.url)) draw(page);
|
|
433
|
+
return result();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
fs.rmSync(out, { recursive: true, force: true });
|
|
437
|
+
files.push(...writeThemeFiles(out, staticDir));
|
|
438
|
+
for (const page of tree) draw(page);
|
|
439
|
+
write(
|
|
440
|
+
path.join(out, "404.html"),
|
|
441
|
+
minifyHtml(
|
|
442
|
+
theme.render({
|
|
443
|
+
kind: "404",
|
|
444
|
+
title: "Page not found",
|
|
445
|
+
url: relUrl(site, "404.html"),
|
|
446
|
+
params: {},
|
|
447
|
+
}),
|
|
448
|
+
),
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
write(
|
|
452
|
+
path.join(out, "sitemap.xml"),
|
|
453
|
+
sitemapXml(
|
|
454
|
+
tree
|
|
455
|
+
.filter((page) => reachable.has(page.url))
|
|
456
|
+
.map((page) => ({
|
|
457
|
+
loc: origin(page.url),
|
|
458
|
+
lastmod: page.params?.lastmod ?? page.date ?? page.params?.date,
|
|
459
|
+
})),
|
|
460
|
+
),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
if (site.feed) {
|
|
464
|
+
write(
|
|
465
|
+
path.join(out, ...section.split("/").filter(Boolean), "atom.xml"),
|
|
466
|
+
atomFeed({
|
|
467
|
+
title: site.feed.title ?? site.title,
|
|
468
|
+
id: origin(relUrl(site, `${section}/`)),
|
|
469
|
+
self: origin(feedUrl),
|
|
470
|
+
author: site.params?.author,
|
|
471
|
+
entries: posts.map((post) => {
|
|
472
|
+
const words = plainText(contentOf(post)).split(" ").filter(Boolean);
|
|
473
|
+
return {
|
|
474
|
+
title: post.title,
|
|
475
|
+
url: origin(post.url),
|
|
476
|
+
date: post.date ?? post.params?.date,
|
|
477
|
+
author: text(post.params?.data?.author) || site.params?.author,
|
|
478
|
+
summary:
|
|
479
|
+
text(post.params?.description) ||
|
|
480
|
+
(words.length > SUMMARY_WORDS ?
|
|
481
|
+
`${words.slice(0, SUMMARY_WORDS).join(" ")}…`
|
|
482
|
+
: words.join(" ")),
|
|
483
|
+
content: contentOf(post),
|
|
484
|
+
};
|
|
485
|
+
}),
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (site.sitemaps) {
|
|
491
|
+
const own = origin(relUrl(site, "sitemap.xml"));
|
|
492
|
+
const index = origin(relUrl(site, "sitemap-index.xml"));
|
|
493
|
+
const all = [own, ...site.sitemaps.filter((loc) => loc !== own)];
|
|
494
|
+
write(path.join(out, "sitemap-index.xml"), sitemapIndexXml(all));
|
|
495
|
+
write(path.join(out, "robots.txt"), robotsTxt([index, ...all]));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return result();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* A package's pages, from its site build.
|
|
503
|
+
*
|
|
504
|
+
* Each body is rendered by the `web` renderer from the markdown the build
|
|
505
|
+
* expanded, and each page carries what it tells the search; see
|
|
506
|
+
* {@link module:engine/site-search}. The site carries what the configuration
|
|
507
|
+
* asks for: `site.tags` as `tagPages`, `site.feed` as the feed's section, each
|
|
508
|
+
* package of `site.sitemaps` as its sitemap's address on the site's origin,
|
|
509
|
+
* and each of `site.mergeIndexes` as the path of its search bundle.
|
|
510
|
+
*
|
|
511
|
+
* @param {object} options - The inputs.
|
|
512
|
+
* @param {object} options.config - The resolved configuration.
|
|
513
|
+
* @param {object} options.built - What `buildSite` returned with `write: false`.
|
|
514
|
+
* @param {{baseURL: string, title: string, params: object}} options.settings -
|
|
515
|
+
* From {@link module:engine/site-config.siteSettings}.
|
|
516
|
+
* @param {readonly {name: string, url: string, children?: object[]}[]} [options.navigation] -
|
|
517
|
+
* The menu.
|
|
518
|
+
* @param {number} [options.year] - The year the footer states.
|
|
519
|
+
* @returns {{site: object, pages: object[], pageOf: (entry: object) => object}}
|
|
520
|
+
* What {@link writeSite} is given, and how one note the build rendered again
|
|
521
|
+
* becomes its page.
|
|
522
|
+
*/
|
|
523
|
+
export function packageSite({ config, built, settings, navigation = [], year }) {
|
|
524
|
+
const renderer = createRenderer("web", { icons: config.icons });
|
|
525
|
+
const resolveSrc = (src) => resolvePathname(src, config)?.web ?? null;
|
|
526
|
+
const { site: from } = built;
|
|
527
|
+
const relative = (file) =>
|
|
528
|
+
file ? path.relative(process.cwd(), file).split(path.sep).join("/") : undefined;
|
|
529
|
+
const world = mapWorld({
|
|
530
|
+
records: from.records,
|
|
531
|
+
foreignIndex: from.foreign?.index ?? new Map(),
|
|
532
|
+
contentBase: config.paths.content,
|
|
533
|
+
base: from.base,
|
|
534
|
+
config,
|
|
535
|
+
});
|
|
536
|
+
const geography = placeGeography(world.places);
|
|
537
|
+
const search = (data) => searchMetadata(data, { geography, index: from.index });
|
|
538
|
+
|
|
539
|
+
/** Each note `site.sections` names, by address, with the section it is the page of. */
|
|
540
|
+
const sectionOf = new Map(
|
|
541
|
+
Object.entries(config.site.sections).map(([name, address]) => [address, name]),
|
|
542
|
+
);
|
|
543
|
+
/**
|
|
544
|
+
* A content page, from what the build made of its note: a section's page
|
|
545
|
+
* for a note `site.sections` names, and a page of the feed's section for
|
|
546
|
+
* a post.
|
|
547
|
+
*/
|
|
548
|
+
const pageOf = ({ page, markdown, figures, data }) => {
|
|
549
|
+
const section = sectionOf.get(page.slug);
|
|
550
|
+
return {
|
|
551
|
+
kind: section ? "section" : "page",
|
|
552
|
+
title: text(data.title),
|
|
553
|
+
url: page.url,
|
|
554
|
+
section: section ?? (isPost(data) ? config.site.feed : ""),
|
|
555
|
+
params: data,
|
|
556
|
+
date: isPost(data) ? data.data?.date : undefined,
|
|
557
|
+
weight: data.weight,
|
|
558
|
+
content: renderer.render(markdown, { runs: from.runs, figures, resolveSrc }),
|
|
559
|
+
source: relative(page.file),
|
|
560
|
+
search: search(data),
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const pages = [];
|
|
565
|
+
for (const home of from.homepages)
|
|
566
|
+
pages.push({
|
|
567
|
+
kind: "home",
|
|
568
|
+
title: text(home.data.title),
|
|
569
|
+
url: from.base,
|
|
570
|
+
params: home.data,
|
|
571
|
+
content: renderer.render(home.body, { runs: from.runs, resolveSrc }),
|
|
572
|
+
source: relative(home.file),
|
|
573
|
+
search: search(home.data),
|
|
574
|
+
});
|
|
575
|
+
for (const entry of from.pages) pages.push(pageOf(entry));
|
|
576
|
+
|
|
577
|
+
const site = {
|
|
578
|
+
title: settings.title,
|
|
579
|
+
baseURL: settings.baseURL,
|
|
580
|
+
params: { ...settings.params, search: config.site.search !== false },
|
|
581
|
+
menu: navigation.map((entry) => ({
|
|
582
|
+
name: entry.name,
|
|
583
|
+
url: entry.url,
|
|
584
|
+
children: (entry.children ?? []).map((child) => ({ name: child.name, url: child.url })),
|
|
585
|
+
})),
|
|
586
|
+
year,
|
|
587
|
+
tagPages: config.site.tags,
|
|
588
|
+
...(config.site.feed ? { feed: { section: config.site.feed } } : {}),
|
|
589
|
+
...(config.site.sitemaps.length ?
|
|
590
|
+
{
|
|
591
|
+
sitemaps: config.site.sitemaps.map(
|
|
592
|
+
(pkg) => new URL(`/${pkg}/sitemap.xml`, settings.baseURL).href,
|
|
593
|
+
),
|
|
594
|
+
}
|
|
595
|
+
: {}),
|
|
596
|
+
...(config.site.mergeIndexes.length ?
|
|
597
|
+
{ mergeIndexes: config.site.mergeIndexes.map((pkg) => `/${pkg}/pagefind/`) }
|
|
598
|
+
: {}),
|
|
599
|
+
};
|
|
600
|
+
return { site, pages, pageOf };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The directory a package's site is written to: its base path under the
|
|
605
|
+
* deployed root, so a site served at `/` is written at the root itself.
|
|
606
|
+
*
|
|
607
|
+
* @param {object} config - The resolved configuration.
|
|
608
|
+
* @param {string} root - The deployed root.
|
|
609
|
+
* @returns {string} The directory.
|
|
610
|
+
*/
|
|
611
|
+
export function siteDirectory(config, root) {
|
|
612
|
+
return path.join(root, ...siteBase(config).split("/").filter(Boolean));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Copy the script fonts the site's pages set their marked text in.
|
|
617
|
+
*
|
|
618
|
+
* @param {object} config - The resolved configuration.
|
|
619
|
+
* @param {string} out - The directory the site is written to.
|
|
620
|
+
* @returns {{findings: object[], count: number, dest?: string}} What checking
|
|
621
|
+
* the installed fonts found, and how many files were copied where. Nothing is
|
|
622
|
+
* copied when there is a finding, or when the package sets no script.
|
|
623
|
+
*/
|
|
624
|
+
export function deliverSiteFonts(config, out) {
|
|
625
|
+
const found = scriptFontFiles(config, ["woff2"]);
|
|
626
|
+
if (!found) return { findings: [], count: 0 };
|
|
627
|
+
const findings = checkScriptFonts(config);
|
|
628
|
+
if (findings.length) return { findings, count: 0 };
|
|
629
|
+
const dest = path.join(out, siteFontsDestination(found.version));
|
|
630
|
+
return { findings, count: copyScriptFonts(config, dest, ["woff2"]), dest };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Build a package's site from its content tree and write it.
|
|
635
|
+
*
|
|
636
|
+
* The site's address, title and menu are read before the tree is, so a site
|
|
637
|
+
* that cannot be described fails before anything is built. A gate that fires
|
|
638
|
+
* stops the build before anything is written; a note in error is written as
|
|
639
|
+
* its page shows it, with the finding in what is returned, which
|
|
640
|
+
* `siteBuildFailed` reads.
|
|
641
|
+
*
|
|
642
|
+
* @param {object} options - The inputs.
|
|
643
|
+
* @param {object} options.config - The resolved configuration.
|
|
644
|
+
* @param {string} options.out - The directory the site is written to, which
|
|
645
|
+
* is the package's own directory under the deployed root.
|
|
646
|
+
* @param {number} [options.year] - The year the footer states.
|
|
647
|
+
* @returns {Promise<{built: object, written: object|null, fonts: object|null,
|
|
648
|
+
* site?: object, pages?: object[], pageOf?: (entry: object) => object}>}
|
|
649
|
+
* What the build found, and, when it found nothing that stops it, what was
|
|
650
|
+
* written, the pages it was written from and how a note rendered again
|
|
651
|
+
* becomes its page. `written` is `null` when a gate fired.
|
|
652
|
+
* @throws {Error} When the site's settings or its cached menu cannot be read.
|
|
653
|
+
*/
|
|
654
|
+
export async function buildPackageSite({ config, out, year }) {
|
|
655
|
+
const settings = siteSettings({ config });
|
|
656
|
+
const navigation = readCachedNavigation(config);
|
|
657
|
+
const sqlTables = await prepareTreeSqlTables(config.paths.content, {
|
|
658
|
+
skipDirectories: config.skipDirectories,
|
|
659
|
+
config,
|
|
660
|
+
audience: "public",
|
|
661
|
+
});
|
|
662
|
+
const built = buildSite({ config, sqlTables, write: false });
|
|
663
|
+
if (gatesFailed(built.gates)) return { built, written: null, fonts: null };
|
|
664
|
+
const { site, pages, pageOf } = packageSite({ config, built, settings, navigation, year });
|
|
665
|
+
const written = writeSite({ site, pages, out });
|
|
666
|
+
const fonts = deliverSiteFonts(config, out);
|
|
667
|
+
return { built, written, fonts, site, pages, pageOf };
|
|
668
|
+
}
|