@heroiclands/package-build 25.6.2 → 25.7.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 +23 -0
- package/README.md +19 -1
- package/bin/content-commands.mjs +64 -79
- package/bin/package-build.mjs +25 -10
- package/ci/wip-workflows.test.mjs +252 -0
- package/content-config.mjs +118 -8
- package/docs/api.md +237 -47
- package/docs/commands.md +99 -35
- package/docs/configuration.md +93 -44
- package/docs/guides/release-and-deploy.md +26 -14
- package/docs/guides/site.md +53 -0
- package/engine/cdn-publish.mjs +164 -6
- package/engine/content-index.mjs +71 -19
- package/engine/content-spans.mjs +18 -1
- package/engine/content-styles.mjs +85 -0
- package/engine/derived-sections.mjs +4 -3
- package/engine/event-views.mjs +6 -4
- package/engine/helpers.mjs +33 -9
- package/engine/index.mjs +1 -0
- package/engine/note-cache.mjs +1107 -0
- package/engine/note-page.mjs +424 -0
- package/engine/pack-config.mjs +47 -4
- package/engine/pathnames.mjs +22 -7
- package/engine/prepared-only.mjs +32 -0
- package/engine/preview-index.mjs +55 -13
- package/engine/site-build.mjs +34 -448
- package/engine/site-config.mjs +33 -4
- package/engine/site-maps.mjs +16 -24
- package/engine/site-origins.mjs +23 -0
- package/engine/site-page.mjs +648 -0
- package/engine/site-render.mjs +142 -10
- package/engine/site-serve.mjs +1 -1
- package/engine/sql-tables.mjs +12 -3
- package/package.json +6 -1
- package/site-theme/static/css/style.css +14 -0
- package/types/content-config.d.mts +34 -0
- package/types/engine/cdn-publish.d.mts +22 -0
- package/types/engine/content-index.d.mts +36 -2
- package/types/engine/content-styles.d.mts +20 -0
- package/types/engine/derived-sections.d.mts +3 -3
- package/types/engine/event-views.d.mts +4 -3
- package/types/engine/helpers.d.mts +18 -1
- package/types/engine/index.d.mts +1 -0
- package/types/engine/note-cache.d.mts +149 -0
- package/types/engine/note-page.d.mts +92 -0
- package/types/engine/pack-config.d.mts +30 -0
- package/types/engine/prepared-only.d.mts +14 -0
- package/types/engine/preview-index.d.mts +35 -0
- package/types/engine/site-build.d.mts +5 -100
- package/types/engine/site-config.d.mts +3 -2
- package/types/engine/site-maps.d.mts +7 -11
- package/types/engine/site-origins.d.mts +9 -0
- package/types/engine/site-page.d.mts +261 -0
- package/types/engine/site-render.d.mts +20 -1
- package/types/engine/sql-tables.d.mts +8 -4
package/engine/site-render.mjs
CHANGED
|
@@ -67,11 +67,10 @@
|
|
|
67
67
|
|
|
68
68
|
import fs from "node:fs";
|
|
69
69
|
import path from "node:path";
|
|
70
|
-
import { fileURLToPath } from "node:url";
|
|
70
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
71
71
|
|
|
72
72
|
import { LEXICAL_LINK_CLASS } from "./dictionary-layout.mjs";
|
|
73
|
-
import {
|
|
74
|
-
import { resolvePathname } from "./pathnames.mjs";
|
|
73
|
+
import { siteBodyRenderer } from "./site-page.mjs";
|
|
75
74
|
import { minifyHtml } from "./html-minify.mjs";
|
|
76
75
|
import { mapWorld } from "./map-places.mjs";
|
|
77
76
|
import {
|
|
@@ -81,7 +80,7 @@ import {
|
|
|
81
80
|
siteFontsDestination,
|
|
82
81
|
} from "./script-font-delivery.mjs";
|
|
83
82
|
import { buildSite, gatesFailed } from "./site-build.mjs";
|
|
84
|
-
import { readCachedNavigation, siteSettings } from "./site-config.mjs";
|
|
83
|
+
import { checkNavigation, readCachedNavigation, siteSettings } from "./site-config.mjs";
|
|
85
84
|
import { atomFeed, robotsTxt, sitemapIndexXml, sitemapXml } from "./site-files.mjs";
|
|
86
85
|
import {
|
|
87
86
|
pageAddress,
|
|
@@ -548,8 +547,7 @@ export function writeSite({
|
|
|
548
547
|
* becomes its page.
|
|
549
548
|
*/
|
|
550
549
|
export function packageSite({ config, built, settings, navigation = [], year }) {
|
|
551
|
-
const
|
|
552
|
-
const resolveSrc = (src) => resolvePathname(src, config)?.web ?? null;
|
|
550
|
+
const renderBody = siteBodyRenderer(config);
|
|
553
551
|
const { site: from } = built;
|
|
554
552
|
const relative = (file) =>
|
|
555
553
|
file ? path.relative(process.cwd(), file).split(path.sep).join("/") : undefined;
|
|
@@ -582,7 +580,7 @@ export function packageSite({ config, built, settings, navigation = [], year })
|
|
|
582
580
|
params: data,
|
|
583
581
|
date: isPost(data) ? data.data?.date : undefined,
|
|
584
582
|
weight: data.weight,
|
|
585
|
-
content:
|
|
583
|
+
content: renderBody(markdown, { runs: from.runs, figures }),
|
|
586
584
|
source: relative(page.file),
|
|
587
585
|
search: search(data),
|
|
588
586
|
};
|
|
@@ -595,7 +593,7 @@ export function packageSite({ config, built, settings, navigation = [], year })
|
|
|
595
593
|
title: text(home.data.title),
|
|
596
594
|
url: from.base,
|
|
597
595
|
params: home.data,
|
|
598
|
-
content:
|
|
596
|
+
content: renderBody(home.body, { runs: from.runs }),
|
|
599
597
|
source: relative(home.file),
|
|
600
598
|
search: search(home.data),
|
|
601
599
|
});
|
|
@@ -657,6 +655,128 @@ export function deliverSiteFonts(config, out) {
|
|
|
657
655
|
return { findings, count: copyScriptFonts(config, dest, ["woff2"]), dest };
|
|
658
656
|
}
|
|
659
657
|
|
|
658
|
+
/**
|
|
659
|
+
* @typedef {object} SiteInputs
|
|
660
|
+
* @property {import("./site-config.mjs").NavigationEntry[]} navigation The selected menu.
|
|
661
|
+
* @property {Record<string, string>} files Text artifacts relative to the site's output.
|
|
662
|
+
*/
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* @callback SiteInputsProvider
|
|
666
|
+
* @param {{config: import("../content-config.mjs").ContentBuildConfig}} options Frozen effective configuration.
|
|
667
|
+
* @returns {SiteInputs|Promise<SiteInputs>} Inputs prepared before content rendering.
|
|
668
|
+
*/
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Refuse a symlink anywhere along a provider module or output path.
|
|
672
|
+
* @param {string} file The absolute path.
|
|
673
|
+
*/
|
|
674
|
+
function checkInputSymlinks(file) {
|
|
675
|
+
const absolute = path.resolve(file);
|
|
676
|
+
let current = path.parse(absolute).root;
|
|
677
|
+
for (const segment of absolute.slice(current.length).split(path.sep)) {
|
|
678
|
+
current = path.join(current, segment);
|
|
679
|
+
const stat = fs.lstatSync(current, { throwIfNoEntry: false });
|
|
680
|
+
if (!stat) break;
|
|
681
|
+
if (stat.isSymbolicLink()) {
|
|
682
|
+
throw new Error(`site.inputs refuses a symlink at ${current}`);
|
|
683
|
+
}
|
|
684
|
+
if (!stat.isDirectory()) break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Read the one optional local input provider. It runs before content work.
|
|
690
|
+
* @param {object} config The effective configuration.
|
|
691
|
+
* @returns {Promise<SiteInputs>} Validated navigation and unvalidated text artifacts.
|
|
692
|
+
*/
|
|
693
|
+
async function readSiteInputs(config) {
|
|
694
|
+
if (!config.site.inputs) return { navigation: readCachedNavigation(config), files: {} };
|
|
695
|
+
const modulePath = path.resolve(config.rootDir, config.site.inputs);
|
|
696
|
+
checkInputSymlinks(modulePath);
|
|
697
|
+
const provider = (await import(pathToFileURL(modulePath).href)).default;
|
|
698
|
+
if (typeof provider !== "function")
|
|
699
|
+
throw new TypeError("site.inputs must export a default function");
|
|
700
|
+
const inputs = await provider({ config });
|
|
701
|
+
if (!inputs || typeof inputs !== "object" || Array.isArray(inputs)) {
|
|
702
|
+
throw new TypeError("site.inputs must return {navigation, files}");
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
navigation: checkNavigation(inputs.navigation, "site.inputs.navigation"),
|
|
706
|
+
files: inputs.files,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Validate every provider artifact before the output tree can be replaced.
|
|
712
|
+
* @param {Record<string, string>} files Text artifacts.
|
|
713
|
+
* @param {object} options Output and builder-owned paths.
|
|
714
|
+
* @param {string} options.out Site output directory.
|
|
715
|
+
* @param {object} options.site Resolved site settings.
|
|
716
|
+
* @param {object[]} options.pages Rendered pages.
|
|
717
|
+
* @returns {[string, string][]} Absolute destination/content pairs.
|
|
718
|
+
*/
|
|
719
|
+
function siteInputFiles(files, { out, site, pages }) {
|
|
720
|
+
if (!files || typeof files !== "object" || Array.isArray(files)) {
|
|
721
|
+
throw new TypeError(
|
|
722
|
+
"site.inputs.files must be a mapping of relative paths to UTF-8 strings",
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
if (!Object.keys(files).length) return [];
|
|
726
|
+
const reservedTrees = new Set(
|
|
727
|
+
[
|
|
728
|
+
"assets",
|
|
729
|
+
"fonts",
|
|
730
|
+
"pagefind",
|
|
731
|
+
"tags",
|
|
732
|
+
...fs.readdirSync(SITE_STATIC_DIR),
|
|
733
|
+
...siteThemeAssets().map((asset) => asset.path.split("/")[0]),
|
|
734
|
+
].map((name) => name.toLowerCase()),
|
|
735
|
+
);
|
|
736
|
+
const owned = [
|
|
737
|
+
"_headers",
|
|
738
|
+
"_redirects",
|
|
739
|
+
"sitemap.xml",
|
|
740
|
+
"sitemap-index.xml",
|
|
741
|
+
"robots.txt",
|
|
742
|
+
"404.html",
|
|
743
|
+
];
|
|
744
|
+
for (const page of siteTree(site, pages))
|
|
745
|
+
owned.push(
|
|
746
|
+
path
|
|
747
|
+
.relative(out, pageFile(site, out, page.url))
|
|
748
|
+
.split(path.sep)
|
|
749
|
+
.join("/"),
|
|
750
|
+
);
|
|
751
|
+
if (site.feed) owned.push(`${site.feed.section}/atom.xml`);
|
|
752
|
+
const ownedPaths = owned.map((name) => name.toLowerCase());
|
|
753
|
+
/** @type {string[]} */
|
|
754
|
+
const seen = [];
|
|
755
|
+
const conflicts = (a, b) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
|
|
756
|
+
return Object.entries(files).map(([name, content]) => {
|
|
757
|
+
const lower = name.toLowerCase();
|
|
758
|
+
if (
|
|
759
|
+
!name.split("/").every((segment) => /^[a-z0-9][a-z0-9._-]*$/i.test(segment)) ||
|
|
760
|
+
typeof content !== "string"
|
|
761
|
+
)
|
|
762
|
+
throw new TypeError(
|
|
763
|
+
`site.inputs.files: ${name} must be a plain relative path with UTF-8 text`,
|
|
764
|
+
);
|
|
765
|
+
if (
|
|
766
|
+
lower.endsWith(".html") ||
|
|
767
|
+
reservedTrees.has(lower.split("/")[0]) ||
|
|
768
|
+
[...ownedPaths, ...seen].some((other) => conflicts(lower, other))
|
|
769
|
+
)
|
|
770
|
+
throw new TypeError(
|
|
771
|
+
`site.inputs.files: ${name} collides with a builder-owned or provider file`,
|
|
772
|
+
);
|
|
773
|
+
seen.push(lower);
|
|
774
|
+
const file = path.join(out, ...name.split("/"));
|
|
775
|
+
checkInputSymlinks(file);
|
|
776
|
+
return [file, content];
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
660
780
|
/**
|
|
661
781
|
* Build a package's site from its content tree and write it.
|
|
662
782
|
*
|
|
@@ -677,11 +797,17 @@ export function deliverSiteFonts(config, out) {
|
|
|
677
797
|
* What the build found, and, when it found nothing that stops it, what was
|
|
678
798
|
* written, the pages it was written from and how a note rendered again
|
|
679
799
|
* becomes its page. `written` is `null` when a gate fired.
|
|
680
|
-
*
|
|
800
|
+
* With `site.inputs`, the local default-exported provider receives `{config}`
|
|
801
|
+
* and supplies the navigation plus text artifacts. All artifact paths are checked
|
|
802
|
+
* before replacing output; artifacts are then written after the renderer clears
|
|
803
|
+
* and writes the site, and included in `written.files`.
|
|
804
|
+
*
|
|
805
|
+
* @throws {Error} When the site's settings, cached menu or input provider fail,
|
|
806
|
+
* or a provider artifact is unsafe or collides with builder-owned output.
|
|
681
807
|
*/
|
|
682
808
|
export async function buildPackageSite({ config, out, year, progress = () => {} }) {
|
|
683
809
|
const settings = siteSettings({ config });
|
|
684
|
-
const navigation =
|
|
810
|
+
const { navigation, files } = await readSiteInputs(config);
|
|
685
811
|
progress("indexing content");
|
|
686
812
|
const sqlTables = await prepareTreeSqlTables(config.paths.content, {
|
|
687
813
|
skipDirectories: config.skipDirectories,
|
|
@@ -691,7 +817,13 @@ export async function buildPackageSite({ config, out, year, progress = () => {}
|
|
|
691
817
|
const built = buildSite({ config, sqlTables, progress });
|
|
692
818
|
if (gatesFailed(built.gates)) return { built, written: null, fonts: null };
|
|
693
819
|
const { site, pages, pageOf } = packageSite({ config, built, settings, navigation, year });
|
|
820
|
+
const artifacts = siteInputFiles(files, { out, site, pages });
|
|
694
821
|
const written = writeSite({ site, pages, out, progress });
|
|
822
|
+
for (const [file, content] of artifacts) {
|
|
823
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
824
|
+
fs.writeFileSync(file, content, "utf8");
|
|
825
|
+
written.files.push(file);
|
|
826
|
+
}
|
|
695
827
|
const fonts = deliverSiteFonts(config, out);
|
|
696
828
|
return { built, written, fonts, site, pages, pageOf };
|
|
697
829
|
}
|
package/engine/site-serve.mjs
CHANGED
|
@@ -94,7 +94,7 @@ export async function serveSite({
|
|
|
94
94
|
}) {
|
|
95
95
|
// Refuse a site that cannot be described before starting its server.
|
|
96
96
|
siteSettings({ config });
|
|
97
|
-
readCachedNavigation(config);
|
|
97
|
+
if (!config.site.inputs) readCachedNavigation(config);
|
|
98
98
|
root = path.resolve(root);
|
|
99
99
|
out = path.resolve(out);
|
|
100
100
|
let timer = null;
|
package/engine/sql-tables.mjs
CHANGED
|
@@ -72,6 +72,7 @@ import { sqlQueriesInMarkdown } from "./markdown-expressions.mjs";
|
|
|
72
72
|
// The record accessors only — see `engine/index-records.mjs`.
|
|
73
73
|
import { authoredFrontmatter, isNoteRecord, noteFile, sortKeysDeep } from "./index-records.mjs";
|
|
74
74
|
import { nameSortOf } from "./name-sort.mjs";
|
|
75
|
+
import { outsideOnly } from "./prepared-only.mjs";
|
|
75
76
|
|
|
76
77
|
/** Rendered in a cell whose value is absent. */
|
|
77
78
|
const EMPTY_CELL = "—";
|
|
@@ -1613,8 +1614,8 @@ export async function prepareInlineSqlExpressions(db, sources) {
|
|
|
1613
1614
|
* surface publishes to.
|
|
1614
1615
|
* @param {string} contentBase - Root of the content tree.
|
|
1615
1616
|
* @param {object} [opts]
|
|
1616
|
-
* @param {string} [opts.only] -
|
|
1617
|
-
* every note.
|
|
1617
|
+
* @param {string|Set<string>} [opts.only] - The note file, or files, to give
|
|
1618
|
+
* sections to; absent, every note.
|
|
1618
1619
|
* @param {Map<string, object>} [opts.foreignIndex] - The fetched indexes, whose
|
|
1619
1620
|
* places, affiliations and works the lists take in.
|
|
1620
1621
|
* @param {object} [opts.config] - The resolved configuration.
|
|
@@ -1685,7 +1686,7 @@ export async function generatedSectionsFor(
|
|
|
1685
1686
|
type: note.type,
|
|
1686
1687
|
shortcode: note.shortcode,
|
|
1687
1688
|
});
|
|
1688
|
-
if (owners.has(key) && (only
|
|
1689
|
+
if (owners.has(key) && !outsideOnly(only, fileOf(note)))
|
|
1689
1690
|
lexical.set(fileOf(note), owners.get(key));
|
|
1690
1691
|
}
|
|
1691
1692
|
return { sections, views: new Set(views.keys()), derived, lexical, declared };
|
|
@@ -1897,6 +1898,9 @@ export async function cachedDependencies(config) {
|
|
|
1897
1898
|
* generated sections take in, where the caller holds them already; absent,
|
|
1898
1899
|
* they are read from the cache.
|
|
1899
1900
|
* @param {import("./content-session.mjs").ContentSyntax} [opts.syntax] - Exact-text scanner projections.
|
|
1901
|
+
* @param {string|Iterable<string>} [opts.only] - One note's file, or several:
|
|
1902
|
+
* answer their directives and give them their generated sections, and no
|
|
1903
|
+
* other note's. The queries still read the whole corpus.
|
|
1900
1904
|
* @returns {Promise<Map<string, object[]>|undefined>} Results by note path,
|
|
1901
1905
|
* carrying `inline` and `pageLists` beside them, or nothing when the tree
|
|
1902
1906
|
* holds no such directive and no note is given a generated section.
|
|
@@ -1912,6 +1916,7 @@ export async function prepareTreeSqlTables(
|
|
|
1912
1916
|
audience = "all",
|
|
1913
1917
|
foreignIndex,
|
|
1914
1918
|
syntax,
|
|
1919
|
+
only,
|
|
1915
1920
|
} = {},
|
|
1916
1921
|
) {
|
|
1917
1922
|
// Imported here rather than at module scope: the index reaches the pack
|
|
@@ -1919,6 +1924,7 @@ export async function prepareTreeSqlTables(
|
|
|
1919
1924
|
// module they load would close a cycle and leave `BasePackCompiler`
|
|
1920
1925
|
// uninitialised for whichever module the runtime happened to load first.
|
|
1921
1926
|
const { indexRecordsFor } = await import("./content-index.mjs");
|
|
1927
|
+
if (only !== undefined && typeof only !== "string") only = new Set(only);
|
|
1922
1928
|
const notes = parsed ?? new Map();
|
|
1923
1929
|
const allRecords =
|
|
1924
1930
|
records ?? indexRecordsFor({ contentBase, config, skipDirectories, parsed: notes });
|
|
@@ -1937,6 +1943,7 @@ export async function prepareTreeSqlTables(
|
|
|
1937
1943
|
for (const record of indexRecords) {
|
|
1938
1944
|
if (!isNoteRecord(record)) continue;
|
|
1939
1945
|
const absPath = noteFile(contentBase, record);
|
|
1946
|
+
if (outsideOnly(only, absPath)) continue;
|
|
1940
1947
|
const { body, frontmatter } = notes.get(absPath) ?? parseMarkdownFile(absPath);
|
|
1941
1948
|
if (
|
|
1942
1949
|
body &&
|
|
@@ -1971,6 +1978,7 @@ export async function prepareTreeSqlTables(
|
|
|
1971
1978
|
foreignIndex: foreign,
|
|
1972
1979
|
config,
|
|
1973
1980
|
all: allRecords,
|
|
1981
|
+
only,
|
|
1974
1982
|
});
|
|
1975
1983
|
if (
|
|
1976
1984
|
!sources.length &&
|
|
@@ -2036,6 +2044,7 @@ export async function prepareTreeSqlTables(
|
|
|
2036
2044
|
parsed,
|
|
2037
2045
|
derived: derivedOnly.derived,
|
|
2038
2046
|
all: allRecords,
|
|
2047
|
+
only,
|
|
2039
2048
|
});
|
|
2040
2049
|
joinGeneratedSections(generated, sources, notes);
|
|
2041
2050
|
const prepared = await prepareSqlTables(db, sources, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.7.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package \u2014 content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"import": "./engine/site-preview.mjs",
|
|
41
41
|
"default": "./engine/site-preview.mjs"
|
|
42
42
|
},
|
|
43
|
+
"./note-page": {
|
|
44
|
+
"types": "./types/engine/note-page.d.mts",
|
|
45
|
+
"import": "./engine/note-page.mjs",
|
|
46
|
+
"default": "./engine/note-page.mjs"
|
|
47
|
+
},
|
|
43
48
|
"./content-config": {
|
|
44
49
|
"types": "./types/content-config.d.mts",
|
|
45
50
|
"import": "./content-config.mjs"
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
Dark fantasy / manuscript aesthetic
|
|
4
4
|
============================================ */
|
|
5
5
|
|
|
6
|
+
/* Each stretch between a note-body comment and its closing comment styles
|
|
7
|
+
what the renderer writes into a page body. A host showing one note's body,
|
|
8
|
+
such as a preview, takes exactly these stretches as the note's styles. */
|
|
9
|
+
/* note-body */
|
|
6
10
|
:root {
|
|
7
11
|
--bg: #0f1923;
|
|
8
12
|
--bg-deeper: #0a1219;
|
|
@@ -67,6 +71,7 @@
|
|
|
67
71
|
--content-width: 100%;
|
|
68
72
|
--content-narrow: 100%;
|
|
69
73
|
}
|
|
74
|
+
/* /note-body */
|
|
70
75
|
|
|
71
76
|
/* ---- Reset ---- */
|
|
72
77
|
|
|
@@ -103,6 +108,7 @@ a:hover {
|
|
|
103
108
|
color: var(--accent-hover);
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
/* note-body */
|
|
106
112
|
/* Inline body links get a subtle underline on hover so they feel
|
|
107
113
|
tactile without competing with headings or navigation. Links
|
|
108
114
|
inside utility contexts (cards, nav, TOC) opt out via more
|
|
@@ -114,6 +120,7 @@ a:hover {
|
|
|
114
120
|
.single-body a:hover {
|
|
115
121
|
border-bottom-color: var(--accent-dim);
|
|
116
122
|
}
|
|
123
|
+
/* /note-body */
|
|
117
124
|
|
|
118
125
|
img {
|
|
119
126
|
max-width: 100%;
|
|
@@ -1000,6 +1007,7 @@ a.tag:hover {
|
|
|
1000
1007
|
line-height: 1.6;
|
|
1001
1008
|
}
|
|
1002
1009
|
|
|
1010
|
+
/* note-body */
|
|
1003
1011
|
/* ---- Article Content ---- */
|
|
1004
1012
|
|
|
1005
1013
|
.single-content {
|
|
@@ -1735,6 +1743,8 @@ a.tag:hover {
|
|
|
1735
1743
|
cursor: help;
|
|
1736
1744
|
}
|
|
1737
1745
|
|
|
1746
|
+
/* /note-body */
|
|
1747
|
+
|
|
1738
1748
|
/* ---- The draft notice ----
|
|
1739
1749
|
|
|
1740
1750
|
A page's statement that its own content is not settled, at the head of the
|
|
@@ -2727,6 +2737,7 @@ summary.info-sidebar-title::after {
|
|
|
2727
2737
|
}
|
|
2728
2738
|
}
|
|
2729
2739
|
|
|
2740
|
+
/* note-body */
|
|
2730
2741
|
/* ---- Cartouches ----
|
|
2731
2742
|
|
|
2732
2743
|
Named-entity SVGs rendered as ornamental accents on culture-flavored
|
|
@@ -2927,6 +2938,7 @@ summary.info-sidebar-title::after {
|
|
|
2927
2938
|
height: 3.25rem;
|
|
2928
2939
|
}
|
|
2929
2940
|
}
|
|
2941
|
+
/* /note-body */
|
|
2930
2942
|
|
|
2931
2943
|
/* ---- Related card (bottom of article) ----
|
|
2932
2944
|
|
|
@@ -2993,6 +3005,7 @@ summary.info-sidebar-title::after {
|
|
|
2993
3005
|
border-bottom-color: var(--accent-dim);
|
|
2994
3006
|
}
|
|
2995
3007
|
|
|
3008
|
+
/* note-body */
|
|
2996
3009
|
/* A drawing the build sets inline — the map in a place's From here section —
|
|
2997
3010
|
scales to its figure, and scrolls rather than shrinking past legibility on
|
|
2998
3011
|
a phone. */
|
|
@@ -3110,3 +3123,4 @@ summary.info-sidebar-title::after {
|
|
|
3110
3123
|
.script :is(em, strong) { font-style:normal; font-weight:inherit; font-synthesis:none; }
|
|
3111
3124
|
.emph-rubric { color:var(--rubric); }
|
|
3112
3125
|
.emph-overline { text-decoration-line:overline; text-decoration-skip-ink:none; }
|
|
3126
|
+
/* /note-body */
|
|
@@ -25,6 +25,26 @@ export function siteBase(config: {
|
|
|
25
25
|
base: string;
|
|
26
26
|
};
|
|
27
27
|
}): string;
|
|
28
|
+
/**
|
|
29
|
+
* Apply validated runtime site URLs to a resolved configuration, without changing it.
|
|
30
|
+
* The caller checks the authored homepage before applying these build options.
|
|
31
|
+
*
|
|
32
|
+
* @param {ContentBuildConfig} config - The resolved authored configuration.
|
|
33
|
+
* @param {object} options - Runtime options.
|
|
34
|
+
* @param {string} [options.root] - Domain from which www and CDN origins derive.
|
|
35
|
+
* Defaults to internal.heroiclands.org; explicit root replaces configured assets.
|
|
36
|
+
* @param {string} [options.baseUrl] - Absolute HTTP(S) site URL ending in `/`;
|
|
37
|
+
* overrides the root-derived site URL.
|
|
38
|
+
* @param {string} [options.assets] - Absolute HTTP(S) asset root; overrides both
|
|
39
|
+
* root-derived and configured assets.
|
|
40
|
+
* @returns {ContentBuildConfig} The effective configuration, frozen. Its homepage
|
|
41
|
+
* and site.assets are the selected inputs for downstream site integrations.
|
|
42
|
+
*/
|
|
43
|
+
export function siteBuildConfig(config: ContentBuildConfig, { root, baseUrl, assets }?: {
|
|
44
|
+
root?: string | undefined;
|
|
45
|
+
baseUrl?: string | undefined;
|
|
46
|
+
assets?: string | undefined;
|
|
47
|
+
}): ContentBuildConfig;
|
|
28
48
|
/**
|
|
29
49
|
* Whether this package publishes the pages its content tree compiles to.
|
|
30
50
|
*
|
|
@@ -700,6 +720,13 @@ export type ContentBuildConfigInput = {
|
|
|
700
720
|
* receive the notes that name none.
|
|
701
721
|
*/
|
|
702
722
|
packs: PackSpec[];
|
|
723
|
+
/**
|
|
724
|
+
* Site settings;
|
|
725
|
+
* inputs names the optional local input provider.
|
|
726
|
+
*/
|
|
727
|
+
site?: (Record<string, any> & {
|
|
728
|
+
inputs?: string;
|
|
729
|
+
}) | undefined;
|
|
703
730
|
/**
|
|
704
731
|
* Layout overrides. See {@link DEFAULT_PATHS}.
|
|
705
732
|
*/
|
|
@@ -802,6 +829,13 @@ export type ContentBuildConfig = {
|
|
|
802
829
|
* the one reader of it.
|
|
803
830
|
*/
|
|
804
831
|
assetRoot: string | null;
|
|
832
|
+
/**
|
|
833
|
+
* Frozen site settings;
|
|
834
|
+
* inputs is the local provider path or an empty string.
|
|
835
|
+
*/
|
|
836
|
+
site: Readonly<Record<string, any> & {
|
|
837
|
+
inputs: string;
|
|
838
|
+
}>;
|
|
805
839
|
paths: Readonly<ResolvedPaths>;
|
|
806
840
|
/**
|
|
807
841
|
* `null` for a `documentation`
|
|
@@ -58,6 +58,24 @@ export function s3Store({ endpoint, bucket, accessKeyId, secretAccessKey, fetch:
|
|
|
58
58
|
* @returns {CdnStore} The read-only store.
|
|
59
59
|
*/
|
|
60
60
|
export function publicStore(host: string, fetchImpl?: typeof fetch): CdnStore;
|
|
61
|
+
/**
|
|
62
|
+
* Internal storage: HTTPS reads and rsync writes of the planned bytes only.
|
|
63
|
+
*
|
|
64
|
+
* The default uses the operator's normal atlas account. An explicitly configured
|
|
65
|
+
* SSH alias may select the forced-rrsync account with a relative `cdn` destination.
|
|
66
|
+
* SSH configuration and its agent supply keys; no remote shell is requested.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} [opts]
|
|
69
|
+
* @param {string} [opts.destination] - `host:/srv/preview/cdn` or `alias:cdn`.
|
|
70
|
+
* @param {typeof fetch} [opts.fetch] - The fetch to use for public reads.
|
|
71
|
+
* @param {(args: string[]) => Promise<unknown>} [opts.transfer] - Run rsync.
|
|
72
|
+
* @returns {CdnStore} The store.
|
|
73
|
+
*/
|
|
74
|
+
export function internalStore({ destination, fetch: fetchImpl, transfer, }?: {
|
|
75
|
+
destination?: string | undefined;
|
|
76
|
+
fetch?: typeof fetch | undefined;
|
|
77
|
+
transfer?: ((args: string[]) => Promise<unknown>) | undefined;
|
|
78
|
+
}): CdnStore;
|
|
61
79
|
/**
|
|
62
80
|
* One picture to publish.
|
|
63
81
|
*
|
|
@@ -220,6 +238,10 @@ export const CDN_ENV: Readonly<{
|
|
|
220
238
|
accessKeyId: string;
|
|
221
239
|
secretAccessKey: string;
|
|
222
240
|
}>;
|
|
241
|
+
/** The internal CDN's public origin. Reads never require SSH credentials. */
|
|
242
|
+
export const INTERNAL_CDN_HOST: "https://cdn.internal.heroiclands.org";
|
|
243
|
+
/** The manual operator's ordinary SSH account on atlas. */
|
|
244
|
+
export const INTERNAL_CDN_DESTINATION: "atlas:/srv/preview/cdn";
|
|
223
245
|
/**
|
|
224
246
|
* One picture to publish.
|
|
225
247
|
*/
|
|
@@ -227,10 +227,17 @@ export function resolveCultures(records: Array<Record<string, any>>, { contentPa
|
|
|
227
227
|
* @param {Iterable<string>} [options.nameTypes] - Additional bare name kinds from configured type declarations.
|
|
228
228
|
* @param {ReturnType<typeof import("./content-timings.mjs").createContentTimings>} [options.timings] -
|
|
229
229
|
* Invocation stage accounting, which records each phase of the derivation.
|
|
230
|
+
* @param {NoteDerivationCache} [options.cache] - Earlier work to answer from:
|
|
231
|
+
* each note's parse, and the records derived from one note alone, which
|
|
232
|
+
* depend on that note and on the context the tree gives every note (the
|
|
233
|
+
* setting date and calendars, the addresses the notes declare, the asset
|
|
234
|
+
* records, the address context and the configured kinds). Not consulted
|
|
235
|
+
* for the records when `syntax` is given. Every pass that reads the whole
|
|
236
|
+
* tree runs whatever the cache holds.
|
|
230
237
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
231
238
|
* not depend on directory-read order.
|
|
232
239
|
*/
|
|
233
|
-
export function collectContentIndex(contentBase: string, { contentPackage, currentDate, defaultCalendar, configFile, rootDir, languages, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, sources, syntax, nameTypes, timings, }: {
|
|
240
|
+
export function collectContentIndex(contentBase: string, { contentPackage, currentDate, defaultCalendar, configFile, rootDir, languages, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, sources, syntax, nameTypes, timings, cache, }: {
|
|
234
241
|
contentPackage: string;
|
|
235
242
|
skipDirectories: readonly string[];
|
|
236
243
|
assetsBase?: string | undefined;
|
|
@@ -270,6 +277,7 @@ export function collectContentIndex(contentBase: string, { contentPackage, curre
|
|
|
270
277
|
hits: any;
|
|
271
278
|
};
|
|
272
279
|
} | undefined;
|
|
280
|
+
cache?: NoteDerivationCache | undefined;
|
|
273
281
|
}): Array<Record<string, any>>;
|
|
274
282
|
/**
|
|
275
283
|
* Serialize records as JSON Lines.
|
|
@@ -316,9 +324,11 @@ export function serializeContentIndex(records: Array<Record<string, any>>): stri
|
|
|
316
324
|
* @param {Map<string, object>} [opts.foreignIndex] - Captured dependency address view.
|
|
317
325
|
* @param {ReturnType<typeof import("./content-timings.mjs").createContentTimings>} [opts.timings] -
|
|
318
326
|
* Invocation stage accounting.
|
|
327
|
+
* @param {NoteDerivationCache} [opts.cache] - Earlier parses and per-note
|
|
328
|
+
* records to answer from; see {@link collectContentIndex}.
|
|
319
329
|
* @returns {object[]} One record per note, plus one per documentation entry.
|
|
320
330
|
*/
|
|
321
|
-
export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectories, problems, parsed, sources, syntax, nameTypes, foreignIndex, timings, }?: {
|
|
331
|
+
export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectories, problems, parsed, sources, syntax, nameTypes, foreignIndex, timings, cache, }?: {
|
|
322
332
|
contentBase?: string | undefined;
|
|
323
333
|
assetsBase?: string | undefined;
|
|
324
334
|
config?: object | undefined;
|
|
@@ -348,6 +358,7 @@ export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectori
|
|
|
348
358
|
hits: any;
|
|
349
359
|
};
|
|
350
360
|
} | undefined;
|
|
361
|
+
cache?: NoteDerivationCache | undefined;
|
|
351
362
|
}): object[];
|
|
352
363
|
/**
|
|
353
364
|
* Prepare the complete bodies a content-index publication describes.
|
|
@@ -409,6 +420,29 @@ export function emitContentIndex({ contentBase, outDir, config, publication }?:
|
|
|
409
420
|
rows: number;
|
|
410
421
|
};
|
|
411
422
|
};
|
|
423
|
+
/**
|
|
424
|
+
* Earlier derivation work {@link collectContentIndex} may answer from, such as
|
|
425
|
+
* {@link module :engine/note-cache.noteTree}'s store.
|
|
426
|
+
*/
|
|
427
|
+
export type NoteDerivationCache = {
|
|
428
|
+
/**
|
|
429
|
+
* - Reads
|
|
430
|
+
* one note, as {@link module :engine/helpers.parseMarkdownFile} does.
|
|
431
|
+
*/
|
|
432
|
+
parse?: typeof import("./helpers.mjs").parseMarkdownFile | undefined;
|
|
433
|
+
/**
|
|
434
|
+
* - A key for the
|
|
435
|
+
* context the tree gives each note's records, or nothing when it has none.
|
|
436
|
+
*/
|
|
437
|
+
context: (value: unknown) => string | undefined;
|
|
438
|
+
/**
|
|
439
|
+
* -
|
|
440
|
+
* The records one note derives: the stored ones when that note's text and
|
|
441
|
+
* the context are unchanged, else `derive()`'s. Either is the caller's to
|
|
442
|
+
* change.
|
|
443
|
+
*/
|
|
444
|
+
records: (absPath: string, context: string | undefined, derive: () => object[]) => object[];
|
|
445
|
+
};
|
|
412
446
|
/**
|
|
413
447
|
* A captured source context, consumed without mutation by the synchronous emitter.
|
|
414
448
|
*/
|
|
@@ -29,5 +29,25 @@ export function highlightRules(scope: string): string;
|
|
|
29
29
|
* @returns {string} The stylesheet.
|
|
30
30
|
*/
|
|
31
31
|
export function siteCodeStylesheet(scope?: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* The CSS the site theme applies to a page body: its custom properties, the
|
|
34
|
+
* body's text, named blocks, figures, verse, footnotes, unresolved and draft
|
|
35
|
+
* links, cartouches, glyphs, alerts, spoilers, dictionary lists and faces,
|
|
36
|
+
* then the highlighted-code rules a site links as `css/code.css`.
|
|
37
|
+
*
|
|
38
|
+
* Read from the theme's own stylesheet, the stretches it marks `note-body`,
|
|
39
|
+
* so a rule changed there changes here. The rules are scoped as the theme
|
|
40
|
+
* scopes them: the body sits in a `.single-body` inside a `.single-content`.
|
|
41
|
+
* The theme's page-wide rules for the reset, the body's typography and the
|
|
42
|
+
* ordinary link come first, scoped to `.single-content`, so a body set in that
|
|
43
|
+
* wrapper reads as the page does whatever the host's own styles; the one
|
|
44
|
+
* exception is the root font size, set on `:root`, since every length the
|
|
45
|
+
* theme writes in `rem` is measured against it.
|
|
46
|
+
*
|
|
47
|
+
* @returns {string} The stylesheet.
|
|
48
|
+
* @throws {Error} When the theme's stylesheet marks no body rules, or lacks
|
|
49
|
+
* one of the page-wide rules.
|
|
50
|
+
*/
|
|
51
|
+
export function noteBodyStylesheet(): string;
|
|
32
52
|
/** The roots the shipped content stylesheet scopes its rules to. */
|
|
33
53
|
export const CONTENT_ROOTS: ":is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)";
|
|
@@ -53,8 +53,8 @@ export function drawMapsFrom(records: object[], { outDir, foreignIndex, config,
|
|
|
53
53
|
* @param {object} [opts.config] - The resolved configuration.
|
|
54
54
|
* @param {(file:string)=>string|undefined} [opts.rawOf] - Captured source locators.
|
|
55
55
|
* @param {object[]} [opts.all=records] - Complete records for settlement validation.
|
|
56
|
-
* @param {string} [opts.only] -
|
|
57
|
-
* every note.
|
|
56
|
+
* @param {string|Set<string>} [opts.only] - The note file, or files, to give
|
|
57
|
+
* sections to; absent, every note.
|
|
58
58
|
* @returns {Map<string, Map<string, string>>} Note file to its sections, each
|
|
59
59
|
* keyed by its slug, for every note given at least one.
|
|
60
60
|
*/
|
|
@@ -64,7 +64,7 @@ export function derivedSections(records: object[], { fileOf, foreignIndex, confi
|
|
|
64
64
|
config?: object | undefined;
|
|
65
65
|
rawOf?: ((file: string) => string | undefined) | undefined;
|
|
66
66
|
all?: object[] | undefined;
|
|
67
|
-
only?: string | undefined;
|
|
67
|
+
only?: string | Set<string> | undefined;
|
|
68
68
|
}): Map<string, Map<string, string>>;
|
|
69
69
|
/**
|
|
70
70
|
* The heading each kind of place is grouped under, in the order the place
|
|
@@ -14,12 +14,13 @@ export function selfForms(record: object): string[];
|
|
|
14
14
|
* @param {Iterable<object>} records - The content-index records.
|
|
15
15
|
* @param {(record: object) => string} fileOf - A record's note file, as the
|
|
16
16
|
* prepared results are keyed.
|
|
17
|
-
* @param {string} [only] -
|
|
18
|
-
* read for them; absent, every note is given its
|
|
17
|
+
* @param {string|Set<string>} [only] - The note file, or files, to give views
|
|
18
|
+
* to, every note's being read for them; absent, every note is given its
|
|
19
|
+
* views.
|
|
19
20
|
* @returns {Promise<Map<string, Map<string, string>>>} Note file to its views,
|
|
20
21
|
* each keyed by its slug, for every note given at least one.
|
|
21
22
|
*/
|
|
22
|
-
export function eventViewSections(db: object, records: Iterable<object>, fileOf: (record: object) => string, only?: string): Promise<Map<string, Map<string, string>>>;
|
|
23
|
+
export function eventViewSections(db: object, records: Iterable<object>, fileOf: (record: object) => string, only?: string | Set<string>): Promise<Map<string, Map<string, string>>>;
|
|
23
24
|
/**
|
|
24
25
|
* The views, in the order
|
|
25
26
|
* {@link module:engine/generated-sections.GENERATED_SECTIONS} sets them in.
|