@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-config.mjs
CHANGED
|
@@ -60,6 +60,9 @@ export const BRAND = Object.freeze({
|
|
|
60
60
|
/** Where the navigation is published. */
|
|
61
61
|
export const NAVIGATION_URL = "https://www.heroiclands.org/nav.json";
|
|
62
62
|
|
|
63
|
+
// Shared with the pathname leaf without importing configuration back into it.
|
|
64
|
+
export { CANONICAL_CDN_ORIGIN } from "./site-origins.mjs";
|
|
65
|
+
|
|
63
66
|
/** The cached navigation's file name. */
|
|
64
67
|
export const NAVIGATION_FILE = "nav.json";
|
|
65
68
|
|
|
@@ -101,7 +104,7 @@ function requireNonEmptyString(value, where) {
|
|
|
101
104
|
/**
|
|
102
105
|
* Check a navigation's shape, and return it.
|
|
103
106
|
*
|
|
104
|
-
* `[{name, url, children?: [{name, url}]}]`, every `url`
|
|
107
|
+
* `[{name, url, children?: [{name, url}]}]`, every `url` a non-empty string. Checked
|
|
105
108
|
* on fetch and again on read: a file that is not a navigation would otherwise
|
|
106
109
|
* reach the menu as `undefined` labels and links.
|
|
107
110
|
*
|
|
@@ -148,7 +151,22 @@ export function navigationCacheDir(config) {
|
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
/**
|
|
151
|
-
*
|
|
154
|
+
* Project a canonical owned chrome link onto the selected site origin.
|
|
155
|
+
* Attribution and ordinary content links do not pass through this function.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} value - A chrome URL.
|
|
158
|
+
* @param {object} config - The effective configuration.
|
|
159
|
+
* @returns {string} The selected URL, or the unchanged external URL.
|
|
160
|
+
*/
|
|
161
|
+
function chromeURL(value, config) {
|
|
162
|
+
if (!URL.canParse(value)) return value;
|
|
163
|
+
const url = new URL(value);
|
|
164
|
+
if (url.origin !== new URL(NAVIGATION_URL).origin) return value;
|
|
165
|
+
return new URL(`${url.pathname}${url.search}${url.hash}`, new URL(config.homepage).origin).href;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The cached navigation, with canonical owned links on the effective homepage origin.
|
|
152
170
|
*
|
|
153
171
|
* **Reads the cache only.** A cold cache is an error naming the command that
|
|
154
172
|
* fills it, rather than a download nobody asked for: a site build that reaches
|
|
@@ -168,7 +186,18 @@ export function readCachedNavigation(config) {
|
|
|
168
186
|
"the site navigation has not been fetched. Run `package-build deps fetch` first.",
|
|
169
187
|
);
|
|
170
188
|
}
|
|
171
|
-
return checkNavigation(JSON.parse(fs.readFileSync(file, "utf8")), file)
|
|
189
|
+
return checkNavigation(JSON.parse(fs.readFileSync(file, "utf8")), file).map((entry) => ({
|
|
190
|
+
...entry,
|
|
191
|
+
url: chromeURL(entry.url, config),
|
|
192
|
+
...(entry.children ?
|
|
193
|
+
{
|
|
194
|
+
children: entry.children.map((child) => ({
|
|
195
|
+
...child,
|
|
196
|
+
url: chromeURL(child.url, config),
|
|
197
|
+
})),
|
|
198
|
+
}
|
|
199
|
+
: {}),
|
|
200
|
+
}));
|
|
172
201
|
}
|
|
173
202
|
|
|
174
203
|
/**
|
|
@@ -252,7 +281,7 @@ export function siteSettings({ config }) {
|
|
|
252
281
|
const params = { description: config.site.description };
|
|
253
282
|
if (config.author?.name) params.author = config.author.name;
|
|
254
283
|
if (config.site.assets) params.cdnBaseURL = config.site.assets;
|
|
255
|
-
params.brand = { ...BRAND };
|
|
284
|
+
params.brand = { ...BRAND, licenseURL: chromeURL(BRAND.licenseURL, config) };
|
|
256
285
|
if (config.site.notfound) params.notfound = structuredClone(config.site.notfound);
|
|
257
286
|
// Named only when the build copies the fonts, so the theme links nothing
|
|
258
287
|
// it does not ship.
|
package/engine/site-maps.mjs
CHANGED
|
@@ -95,20 +95,33 @@ export function inlineSvg(svg) {
|
|
|
95
95
|
* @param {object} opts.config - The resolved configuration.
|
|
96
96
|
* @param {string} opts.base - The site base every page is served under,
|
|
97
97
|
* ending in a slash: what every name in a drawing links through.
|
|
98
|
+
* @param {Iterable<string>} [opts.only] - The places to draw, by shortcode,
|
|
99
|
+
* of those given a map; absent, every one.
|
|
100
|
+
* @param {string} [opts.outDir] - Where to draw, emptied first;
|
|
101
|
+
* {@link SITE_MAP_DIR} under the package root by default.
|
|
98
102
|
* @returns {{maps: Map<string, SiteMap>, findings: Array<{file: string,
|
|
99
103
|
* line?: number, column?: number, severity: "error"|"warning",
|
|
100
104
|
* message: string}>}} The drawings, keyed by the file name a **From here**
|
|
101
105
|
* section names, and warnings for relations that name no place.
|
|
102
106
|
*/
|
|
103
|
-
export function drawSiteMaps({
|
|
107
|
+
export function drawSiteMaps({
|
|
108
|
+
records,
|
|
109
|
+
foreignIndex,
|
|
110
|
+
config,
|
|
111
|
+
base,
|
|
112
|
+
only,
|
|
113
|
+
outDir = path.join(config.rootDir, ...SITE_MAP_DIR.split("/")),
|
|
114
|
+
}) {
|
|
104
115
|
/** @type {Map<string, SiteMap>} */
|
|
105
116
|
const maps = new Map();
|
|
106
|
-
const
|
|
117
|
+
const wanted = only ? new Set(only) : undefined;
|
|
107
118
|
// Every run draws afresh: a place that lost its relations must not keep
|
|
108
119
|
// the map an earlier run drew.
|
|
109
120
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
110
121
|
|
|
111
|
-
const centres = placesWithMaps(records, { foreignIndex, config })
|
|
122
|
+
const centres = placesWithMaps(records, { foreignIndex, config }).filter(
|
|
123
|
+
(centre) => !wanted || wanted.has(centre),
|
|
124
|
+
);
|
|
112
125
|
if (centres.length === 0) return { maps, findings: [] };
|
|
113
126
|
const world = mapWorld({
|
|
114
127
|
records,
|
|
@@ -126,24 +139,3 @@ export function drawSiteMaps({ records, foreignIndex, config, base }) {
|
|
|
126
139
|
}
|
|
127
140
|
return { maps, findings };
|
|
128
141
|
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* The figure the website sets a drawing in: the SVG inline, so its place names
|
|
132
|
-
* stay links, with no blank line inside it — a blank line would end the HTML
|
|
133
|
-
* block and hand the rest of the drawing to the Markdown renderer.
|
|
134
|
-
*
|
|
135
|
-
* @param {string} svg - The drawing, as {@link inlineSvg} wrote it.
|
|
136
|
-
* @param {string} alt - What the drawing shows, for a screen reader.
|
|
137
|
-
* @returns {string} One HTML block.
|
|
138
|
-
*/
|
|
139
|
-
export function inlineDrawingFigure(svg, alt) {
|
|
140
|
-
const label = String(alt ?? "").replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
141
|
-
const body = String(svg)
|
|
142
|
-
.split("\n")
|
|
143
|
-
.filter((line) => line.trim() !== "")
|
|
144
|
-
.join("\n");
|
|
145
|
-
return (
|
|
146
|
-
`<figure class="note-image note-image-full-width" role="img" ` +
|
|
147
|
-
`aria-label="${label}">\n${body}\n</figure>`
|
|
148
|
-
);
|
|
149
|
-
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
* The owned CDN origin, re-exported by site-config beside the chrome addresses.
|
|
16
|
+
* Kept in a leaf because site-config reaches pathnames through configuration and
|
|
17
|
+
* content rendering; importing site-config from pathnames would close that cycle.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The canonical public origin whose asset URLs follow the selected build host. */
|
|
23
|
+
export const CANONICAL_CDN_ORIGIN = "https://cdn.heroiclands.org";
|