@wizzlethorpe/vaults 0.14.0 → 0.15.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/README.md +2 -2
- package/dist/asset-refs.js +18 -10
- package/dist/asset-refs.js.map +1 -1
- package/dist/build.js +159 -70
- package/dist/build.js.map +1 -1
- package/dist/commands/build.js +1 -54
- package/dist/commands/build.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/escape.js +20 -0
- package/dist/escape.js.map +1 -1
- package/dist/foundry-adventure.js +86 -0
- package/dist/foundry-adventure.js.map +1 -0
- package/dist/foundry-defaults.js +65 -0
- package/dist/foundry-defaults.js.map +1 -0
- package/dist/foundry-grafts.js +610 -0
- package/dist/foundry-grafts.js.map +1 -0
- package/dist/foundry-html.js +196 -0
- package/dist/foundry-html.js.map +1 -0
- package/dist/foundry-importer.bundle.js +39 -3
- package/dist/foundry-meta.js +15 -243
- package/dist/foundry-meta.js.map +1 -1
- package/dist/foundry-module-journal.js +24 -3
- package/dist/foundry-module-journal.js.map +1 -1
- package/dist/foundry-module.js +19 -12
- package/dist/foundry-module.js.map +1 -1
- package/dist/foundry-types.js +28 -0
- package/dist/foundry-types.js.map +1 -0
- package/dist/foundry-version.js +30 -0
- package/dist/foundry-version.js.map +1 -0
- package/dist/frontmatter-defaults.js +1 -1
- package/dist/frontmatter-defaults.js.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/manifest.js +1 -1
- package/dist/manifest.js.map +1 -1
- package/dist/migrate/0.15-foundry-patch-keys.js +51 -0
- package/dist/migrate/0.15-foundry-patch-keys.js.map +1 -0
- package/dist/migrate/0.15-foundry-pinned-id.js +68 -0
- package/dist/migrate/0.15-foundry-pinned-id.js.map +1 -0
- package/dist/migrate/files.js +66 -0
- package/dist/migrate/files.js.map +1 -0
- package/dist/migrate/registry.js +4 -0
- package/dist/migrate/registry.js.map +1 -1
- package/dist/migrate/run.js +36 -2
- package/dist/migrate/run.js.map +1 -1
- package/dist/render/auth-template.js +9 -8
- package/dist/render/auth-template.js.map +1 -1
- package/dist/render/handlers/assets.js +5 -34
- package/dist/render/handlers/assets.js.map +1 -1
- package/dist/render/handlers/builtin/battlemap.js +4 -1
- package/dist/render/handlers/builtin/battlemap.js.map +1 -1
- package/dist/render/handlers/builtin/fm-code.js +2 -2
- package/dist/render/handlers/builtin/fm-code.js.map +1 -1
- package/dist/render/handlers/builtin/fvtt-link.js +39 -0
- package/dist/render/handlers/builtin/fvtt-link.js.map +1 -0
- package/dist/render/handlers/builtin/index.js +2 -1
- package/dist/render/handlers/builtin/index.js.map +1 -1
- package/dist/render/handlers/builtin/statblock.js +0 -4
- package/dist/render/handlers/builtin/statblock.js.map +1 -1
- package/dist/render/handlers/types.js.map +1 -1
- package/dist/scan.js +0 -8
- package/dist/scan.js.map +1 -1
- package/dist/settings.js +57 -28
- package/dist/settings.js.map +1 -1
- package/dist/zip.js +78 -0
- package/dist/zip.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ Cloudflare Pages ← per-user, your account
|
|
|
45
45
|
- **Per-tier deploys.** A page tagged `role: dm` in its frontmatter only includes to the dm variant. Public visitors *cannot* fetch it; the file structurally doesn't exist in their variant.
|
|
46
46
|
- **Inline gating with callouts.** Drop a `> [!dm]` callout in an otherwise public page; the entire block is stripped from the public deploy. Same for any other configured role.
|
|
47
47
|
- **Images and media are gated too.** Only images, audio, video, PDFs, and EPUBs embedded by visible pages are copied into a given variant. Unknown extensions are skipped by default (toggle `include_unknown_files`).
|
|
48
|
-
- **
|
|
48
|
+
- **Foundry integration.** The build compiles each role's pages into `_foundry/grafts.json`, an entry list the [Foundry module](https://github.com/wizzlethorpe/vaults) hands to [graft](https://github.com/wizzlethorpe/graft) to build on the reader's own machine. Bodies and art travel as references and are pulled through `/_batch` (text) and `/_batch-images` (binary) in the reader's own role variant, so a player never receives the GM's rendering of a page.
|
|
49
49
|
- **Bases support.** `.base` files render as cards / table / list inside the wiki, just like inside Obsidian.
|
|
50
50
|
- **LaTeX math.** `$inline$` and `$$display$$` math render server-side via KaTeX, matching Obsidian's syntax. The stylesheet and fonts are self-hosted and only ship for vaults that contain math.
|
|
51
51
|
- **Social meta.** OG / Twitter card tags are auto-generated. Pages without an explicit `image:` frontmatter use the first body embed (toggle with `auto_image`).
|
|
@@ -217,7 +217,7 @@ Multi-role deploys include with a small Cloudflare Pages Function (`_middleware.
|
|
|
217
217
|
- **Issues bearer tokens** through an OAuth-style `/connect` flow used by the [Foundry module](https://github.com/wizzlethorpe/vaults).
|
|
218
218
|
- **Handles Patreon login** at `/auth/patreon/start` and `/auth/patreon/callback`, and **OIDC login** at `/auth/oidc/start` and `/auth/oidc/callback`, when configured.
|
|
219
219
|
- **Exposes** `/_batch` (text) and `/_batch-images` (binary) for bulk content sync.
|
|
220
|
-
- **Publishes**
|
|
220
|
+
- **Publishes** `_foundry/module.json`, `module.zip` and `version.json` ungated, so Foundry can install the vault's module without a credential, while the entry list behind them stays role-gated.
|
|
221
221
|
|
|
222
222
|
Tokens are stateless HMAC-signed JWTs; revocation = rotate `SESSION_SECRET` via `vaults push --rotate-secret`.
|
|
223
223
|
|
package/dist/asset-refs.js
CHANGED
|
@@ -18,7 +18,9 @@ import { foundryManifestPaths } from "./render/handlers/builtin/foundry-manifest
|
|
|
18
18
|
import { IMAGE_EXT_RE } from "./render/extensions.js";
|
|
19
19
|
import { slugify } from "./render/slug.js";
|
|
20
20
|
import { loadDataJson } from "./foundry-meta.js";
|
|
21
|
-
|
|
21
|
+
// The optional backslash matters: a table cell has to escape the size hint's
|
|
22
|
+
// pipe, and keeping that backslash in the name misses the index silently.
|
|
23
|
+
const EMBED_RE = /!\[\[([^\[\]|#\n]+?)\\?(?:\|[^\[\]#\n]*)?\]\]/g;
|
|
22
24
|
// A ```gallery code block. Its body lists images by name (one per line,
|
|
23
25
|
// optional `| caption`), which the gallery handler renders but the source
|
|
24
26
|
// scanners would otherwise never see — so we read the block here to stage
|
|
@@ -29,7 +31,7 @@ const GALLERY_BLOCK_RE = /^```gallery[^\n]*\n([\s\S]*?)^```/gm;
|
|
|
29
31
|
// internal page links (e.g. `(href)` without an extension).
|
|
30
32
|
const MD_LINK_RE = /\[[^\]]*\]\(([^)\s]+\.[a-z0-9]+)(?:\s+["'][^"']*["'])?\)/gi;
|
|
31
33
|
// `[[file.ext]]` and `![[file.ext]]` — Obsidian-flavoured wikilinks/embeds.
|
|
32
|
-
const WIKI_LINK_RE = /!?\[\[([^\[\]|#\n]+\.[a-z0-9]+)(?:\|[^\[\]#\n]*)?(?:#[^\[\]\n]*)?\]\]/gi;
|
|
34
|
+
const WIKI_LINK_RE = /!?\[\[([^\[\]|#\n]+\.[a-z0-9]+)\\?(?:\|[^\[\]#\n]*)?(?:#[^\[\]\n]*)?\]\]/gi;
|
|
33
35
|
/** Image basenames referenced inside a page's ```gallery blocks. */
|
|
34
36
|
function galleryImageNames(source) {
|
|
35
37
|
const names = [];
|
|
@@ -108,7 +110,7 @@ export async function copyReferencedImages(visibleSources, visibleMetas, imageIn
|
|
|
108
110
|
}
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
|
-
// Image refs inside the page's foundry.
|
|
113
|
+
// Image refs inside the page's foundry.patch_json (Scene backgrounds, tiles).
|
|
112
114
|
for (const path of p.foundryAssets ?? []) {
|
|
113
115
|
if (!IMAGE_EXT_RE.test(path))
|
|
114
116
|
continue;
|
|
@@ -117,12 +119,14 @@ export async function copyReferencedImages(visibleSources, visibleMetas, imageIn
|
|
|
117
119
|
refs.add(image.outputPath);
|
|
118
120
|
}
|
|
119
121
|
}
|
|
122
|
+
const copied = [];
|
|
120
123
|
for (const outputPath of refs) {
|
|
121
124
|
const src = join(stagingDir, outputPath);
|
|
122
125
|
const dst = join(variantDir, outputPath);
|
|
123
126
|
await mkdir(dirname(dst), { recursive: true });
|
|
124
127
|
try {
|
|
125
128
|
await copyFile(src, dst);
|
|
129
|
+
copied.push(outputPath);
|
|
126
130
|
}
|
|
127
131
|
catch (err) {
|
|
128
132
|
// Source may legitimately be missing if the file is in the index but
|
|
@@ -130,9 +134,10 @@ export async function copyReferencedImages(visibleSources, visibleMetas, imageIn
|
|
|
130
134
|
console.warn(` warning: could not copy image ${outputPath}: ${err.message}`);
|
|
131
135
|
}
|
|
132
136
|
}
|
|
137
|
+
return copied;
|
|
133
138
|
}
|
|
134
139
|
/** Collect the `@vault/...` paths a page's foundry block references, from both
|
|
135
|
-
* `foundry.
|
|
140
|
+
* `foundry.patch_json` and `foundry.patch`. A Scene's bulk asset refs
|
|
136
141
|
* (backgrounds, ambient sounds, tiles) live in that JSON content, and a token's
|
|
137
142
|
* ring subject lives in the inline `data` overlay; neither appears anywhere the
|
|
138
143
|
* per-variant asset scanners look, so without this they never ship and Foundry
|
|
@@ -148,20 +153,20 @@ export async function collectDataJsonVaultRefs(vaultPath, fm, pagePath) {
|
|
|
148
153
|
if (path)
|
|
149
154
|
out.push(path);
|
|
150
155
|
});
|
|
151
|
-
const rel = block["
|
|
156
|
+
const rel = block["patch_json"];
|
|
152
157
|
if (typeof rel === "string" && rel.trim()) {
|
|
153
158
|
const parsed = await loadDataJson(vaultPath, rel.trim(), pagePath);
|
|
154
159
|
if (parsed !== null)
|
|
155
160
|
collect(parsed);
|
|
156
161
|
}
|
|
157
|
-
collect(block["
|
|
162
|
+
collect(block["patch"]);
|
|
158
163
|
return out;
|
|
159
164
|
}
|
|
160
165
|
/**
|
|
161
166
|
* Visit every string value reachable from `value` (object / array / scalar)
|
|
162
167
|
* and call `fn` once per string. Used to surface `@vault/PATH` references
|
|
163
|
-
* inside parsed frontmatter (e.g., a Scene's `foundry.
|
|
164
|
-
* or a Playlist's `foundry.
|
|
168
|
+
* inside parsed frontmatter (e.g., a Scene's `foundry.patch.background.src`
|
|
169
|
+
* or a Playlist's `foundry.patch.sounds[N].path`) so the per-variant asset
|
|
165
170
|
* scanner can include those files alongside body-referenced ones.
|
|
166
171
|
*/
|
|
167
172
|
export function forEachString(value, fn) {
|
|
@@ -204,7 +209,7 @@ export async function copyReferencedPassthroughs(visibleSources, visibleMetas, p
|
|
|
204
209
|
*/
|
|
205
210
|
manifestDownloads = new Map()) {
|
|
206
211
|
if (passthroughIndex.size === 0)
|
|
207
|
-
return;
|
|
212
|
+
return [];
|
|
208
213
|
const refs = new Set();
|
|
209
214
|
for (const source of visibleSources.values()) {
|
|
210
215
|
for (const m of source.matchAll(WIKI_LINK_RE)) {
|
|
@@ -252,23 +257,26 @@ manifestDownloads = new Map()) {
|
|
|
252
257
|
refs.add(entry.outputPath);
|
|
253
258
|
}
|
|
254
259
|
});
|
|
255
|
-
// Audio/video/pdf refs inside the page's foundry.
|
|
260
|
+
// Audio/video/pdf refs inside the page's foundry.patch_json (ambient sounds).
|
|
256
261
|
for (const path of p.foundryAssets ?? []) {
|
|
257
262
|
const entry = passthroughIndex.get(path);
|
|
258
263
|
if (entry)
|
|
259
264
|
refs.add(entry.outputPath);
|
|
260
265
|
}
|
|
261
266
|
}
|
|
267
|
+
const copied = [];
|
|
262
268
|
for (const outputPath of refs) {
|
|
263
269
|
const src = join(stagingDir, outputPath);
|
|
264
270
|
const dst = join(variantDir, outputPath);
|
|
265
271
|
await mkdir(dirname(dst), { recursive: true });
|
|
266
272
|
try {
|
|
267
273
|
await copyFile(src, dst);
|
|
274
|
+
copied.push(outputPath);
|
|
268
275
|
}
|
|
269
276
|
catch (err) {
|
|
270
277
|
console.warn(` warning: could not copy ${outputPath}: ${err.message}`);
|
|
271
278
|
}
|
|
272
279
|
}
|
|
280
|
+
return copied;
|
|
273
281
|
}
|
|
274
282
|
//# sourceMappingURL=asset-refs.js.map
|
package/dist/asset-refs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-refs.js","sourceRoot":"","sources":["../src/asset-refs.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,iCAAiC;AACjC,EAAE;AACF,wEAAwE;AACxE,6EAA6E;AAC7E,wEAAwE;AACxE,0EAA0E;AAE1E,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+CAA+C,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"asset-refs.js","sourceRoot":"","sources":["../src/asset-refs.ts"],"names":[],"mappings":"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"}
|
package/dist/build.js
CHANGED
|
@@ -9,22 +9,20 @@ import { htmlEscape } from "./escape.js";
|
|
|
9
9
|
import { collectDataJsonVaultRefs, copyReferencedImages, copyReferencedPassthroughs, } from "./asset-refs.js";
|
|
10
10
|
import { downloadFilePaths } from "./render/handlers/builtin/download.js";
|
|
11
11
|
import { foundryManifestPaths, manifestDownloadPath } from "./render/handlers/builtin/foundry-manifest.js";
|
|
12
|
-
import {
|
|
13
|
-
import { collectBodyMeta, warnFoundryDocCollisions } from "./foundry-meta.js";
|
|
12
|
+
import { warnFoundryDocCollisions } from "./foundry-meta.js";
|
|
14
13
|
import { compressImage } from "./images.js";
|
|
15
14
|
import { IMAGE_EXT_RE, PASSTHROUGH_EXT_RE, COMPRESSIBLE_EXT_RE, } from "./render/extensions.js";
|
|
16
15
|
import { buildFavicon } from "./favicon.js";
|
|
17
16
|
import { renderMarkdown } from "./render/pipeline.js";
|
|
18
17
|
import { extractH1 } from "./render/frontmatter.js";
|
|
19
18
|
import { renderLayout, render404 } from "./render/layout.js";
|
|
20
|
-
import { writeFoundryImporter } from "./foundry-importer.js";
|
|
21
19
|
import { slugify } from "./render/slug.js";
|
|
22
20
|
import { buildPreview } from "./render/preview.js";
|
|
23
21
|
import { resolvePageImage } from "./render/cover.js";
|
|
24
22
|
import { DEFAULT_CSS, renderThemeOverride } from "./render/styles.js";
|
|
25
23
|
import { loadObsidianSnippets } from "./obsidian.js";
|
|
26
24
|
import { loadSettings, writeSettings, SETTINGS_FILE } from "./settings.js";
|
|
27
|
-
import { loadConfig } from "./config.js";
|
|
25
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
28
26
|
import { applyFrontmatterDefaults, compileFrontmatterRules } from "./frontmatter-defaults.js";
|
|
29
27
|
import matter from "gray-matter";
|
|
30
28
|
import { renderAuthMiddleware, renderLoginPage } from "./render/auth-template.js";
|
|
@@ -36,6 +34,11 @@ import { bundleHandlerAssets } from "./render/handlers/assets.js";
|
|
|
36
34
|
import { runMigrations } from "./migrate/run.js";
|
|
37
35
|
import { cacheDir } from "./paths.js";
|
|
38
36
|
import { formatDuration, pMap, Progress } from "./util.js";
|
|
37
|
+
import { buildGrafts, contentHash, moduleManifest, moduleGrafts, packsFor, pagesFrom, withFolderIndexes } from "./foundry-grafts.js";
|
|
38
|
+
import { toFoundryHtml, dualVariantBody } from "./foundry-html.js";
|
|
39
|
+
import { zip } from "./zip.js";
|
|
40
|
+
import { moduleVersion } from "./foundry-version.js";
|
|
41
|
+
import { loadDataJson } from "./foundry-meta.js";
|
|
39
42
|
/**
|
|
40
43
|
* Output layout when there are multiple roles:
|
|
41
44
|
*
|
|
@@ -368,7 +371,7 @@ export async function buildSite(input) {
|
|
|
368
371
|
if (roleErrors.length > 0) {
|
|
369
372
|
throw new Error(`Unknown frontmatter role on ${roleErrors.length} page(s) (build aborted so gated pages can't leak):\n${roleErrors.join("\n")}`);
|
|
370
373
|
}
|
|
371
|
-
// Stage assets referenced inside each page's foundry.
|
|
374
|
+
// Stage assets referenced inside each page's foundry.patch_json (Scene
|
|
372
375
|
// backgrounds / ambient sounds / tile art live in that JSON, not the page
|
|
373
376
|
// frontmatter, so the asset scanners below consult p.foundryAssets).
|
|
374
377
|
await Promise.all(allPageMetas.map(async (p) => {
|
|
@@ -473,18 +476,8 @@ export async function buildSite(input) {
|
|
|
473
476
|
.update(DEFAULT_CSS + themeOverride + userCss + handlerAssets.js + handlerAssets.css + katexVersion)
|
|
474
477
|
.digest("hex")
|
|
475
478
|
.slice(0, 10);
|
|
476
|
-
// Foundry importer bundle: one ESM file the Foundry module fetches at
|
|
477
|
-
// sync time. Skipped entirely when the vault has opted out of the Foundry
|
|
478
|
-
// integration — it is ~60KB shipped to every deploy, and a course site or
|
|
479
|
-
// research wiki will never fetch it.
|
|
480
479
|
const foundryEnabled = settings.values.foundry.package !== "none";
|
|
481
|
-
|
|
482
|
-
await writeFoundryImporter(opts.outputDir);
|
|
483
|
-
// Foundry-import bundles are written per-variant inside the role loop
|
|
484
|
-
// below (instead of at the root) so the middleware role-gates them. A
|
|
485
|
-
// public visitor can't fetch the dm-tier handler bundle even if it
|
|
486
|
-
// contains different content. The path stays `/_handlers.foundry.{js,css}`
|
|
487
|
-
// — the middleware rewrites root requests to the matching variant.
|
|
480
|
+
const foundryPackaging = settings.values.foundry.package === "adventure" ? "adventure" : "compendium";
|
|
488
481
|
// Favicon; either user-supplied via settings.favicon, or a generated
|
|
489
482
|
// default with the vault's first letter in accent on the theme background.
|
|
490
483
|
try {
|
|
@@ -526,11 +519,64 @@ export async function buildSite(input) {
|
|
|
526
519
|
meta.coverImage = cover;
|
|
527
520
|
}
|
|
528
521
|
warnFoundryDocCollisions(allPageMetas);
|
|
522
|
+
// Said once for the vault, not once per role: it is a fact about the
|
|
523
|
+
// settings, and the per-variant emitter would repeat it for each.
|
|
524
|
+
if (foundryEnabled && !settings.values.foundry.core_version
|
|
525
|
+
&& allPageMetas.some((p) => p.frontmatter?.["foundry"]?.source)) {
|
|
526
|
+
console.warn(" foundry.core_version is not set, so documents carry no _stats.coreVersion."
|
|
527
|
+
+ " Foundry rejects those and builds a degraded copy instead: a Scene loses its levels."
|
|
528
|
+
+ " Set it to the full Foundry version your exported JSON came from, e.g. 14.359.");
|
|
529
|
+
}
|
|
530
|
+
// The module is only emitted when there is a URL to fetch the vault from,
|
|
531
|
+
// so without one the deploy carries content Foundry has no way to install.
|
|
532
|
+
if (foundryEnabled && !opts.siteUrl) {
|
|
533
|
+
console.warn(" site_url is not set, so no Foundry module is written."
|
|
534
|
+
+ " The vault deploys, but there is nothing to install in Foundry."
|
|
535
|
+
+ " Set it to the URL this vault is served from, e.g. https://notes.example.com.");
|
|
536
|
+
}
|
|
537
|
+
// Read each page's `foundry.patch_json` once, before any variant is
|
|
538
|
+
// rendered: the file is the same whoever is reading, and a Scene sidecar is
|
|
539
|
+
// the largest thing in the vault to be re-parsing per role. It stays separate
|
|
540
|
+
// from the page's inline patch, because it is a weaker statement than one —
|
|
541
|
+
// an export carries whatever Foundry had, including its placeholders.
|
|
542
|
+
const foundryPatches = new Map();
|
|
543
|
+
await Promise.all(allPageMetas.map(async (p) => {
|
|
544
|
+
const fo = p.frontmatter?.["foundry"];
|
|
545
|
+
if (!fo || typeof fo !== "object" || Array.isArray(fo))
|
|
546
|
+
return;
|
|
547
|
+
const block = fo;
|
|
548
|
+
const ref = block["patch_json"];
|
|
549
|
+
if (typeof ref !== "string" || !ref.trim())
|
|
550
|
+
return;
|
|
551
|
+
const loaded = await loadDataJson(opts.vaultPath, ref.trim(), p.path);
|
|
552
|
+
if (!loaded || typeof loaded !== "object" || Array.isArray(loaded))
|
|
553
|
+
return;
|
|
554
|
+
foundryPatches.set(p.path, loaded);
|
|
555
|
+
}));
|
|
556
|
+
// A module id from the vault name: stable, lowercase, no spaces. It names
|
|
557
|
+
// the packs too, so changing it orphans what a reader already built.
|
|
558
|
+
const foundryModuleId = opts.vaultName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
|
|
559
|
+
|| "vault";
|
|
529
560
|
// ── Per-role variant builds ─────────────────────────────────────────────
|
|
530
561
|
const perRolePageCount = {};
|
|
531
562
|
const collapseToRoot = roles.length === 1;
|
|
532
563
|
let defaultRolePagePaths = [];
|
|
533
564
|
let katexCopied = false;
|
|
565
|
+
// What the Foundry provider compares a cached file against, so a rebuild
|
|
566
|
+
// re-fetches only what changed. Keyed "<variant>/<path>", and hashed once
|
|
567
|
+
// per asset rather than once per variant: every variant copies the same
|
|
568
|
+
// staged file. Roles run least privileged first, so by the time a variant
|
|
569
|
+
// writes its grafts.json the map holds every variant it can reference.
|
|
570
|
+
const assetHashes = new Map();
|
|
571
|
+
const hashOf = new Map();
|
|
572
|
+
const hashAsset = async (path, dir) => {
|
|
573
|
+
let digest = hashOf.get(path);
|
|
574
|
+
if (!digest) {
|
|
575
|
+
digest = createHash("md5").update(await readFile(join(dir, path))).digest("hex").slice(0, 16);
|
|
576
|
+
hashOf.set(path, digest);
|
|
577
|
+
}
|
|
578
|
+
return digest;
|
|
579
|
+
};
|
|
534
580
|
for (const role of roles) {
|
|
535
581
|
const variantDir = collapseToRoot
|
|
536
582
|
? opts.outputDir
|
|
@@ -572,6 +618,9 @@ export async function buildSite(input) {
|
|
|
572
618
|
allWarnings: opts.allWarnings,
|
|
573
619
|
});
|
|
574
620
|
perRolePageCount[role] = stats.pageCount;
|
|
621
|
+
for (const path of stats.assetPaths) {
|
|
622
|
+
assetHashes.set(`${role}/${path}`, await hashAsset(path, variantDir));
|
|
623
|
+
}
|
|
575
624
|
// Only the default (lowest) role feeds the sitemap; see writeSitemap.
|
|
576
625
|
if (role === roles[0])
|
|
577
626
|
defaultRolePagePaths = stats.pagePaths;
|
|
@@ -584,36 +633,98 @@ export async function buildSite(input) {
|
|
|
584
633
|
await copyKatexAssets(join(opts.outputDir, "katex"));
|
|
585
634
|
katexCopied = true;
|
|
586
635
|
}
|
|
587
|
-
//
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
636
|
+
// The entry list graft builds from, inside the variant directory so the
|
|
637
|
+
// auth middleware gates it: a role only ever receives the pages it may
|
|
638
|
+
// read, and the GM's variant is the one that lists everything.
|
|
639
|
+
if (foundryEnabled) {
|
|
640
|
+
const graftPages = withFolderIndexes(pagesFrom(allPageMetas, visibleRoles, foundryPatches), roles);
|
|
641
|
+
const grafts = buildGrafts(graftPages, {
|
|
642
|
+
vaultId: foundryModuleId,
|
|
643
|
+
roles,
|
|
644
|
+
playerRole: settings.values.foundry.player_role,
|
|
645
|
+
buildRole: role,
|
|
646
|
+
packs: packsFor(foundryModuleId),
|
|
647
|
+
coreVersion: settings.values.foundry.core_version,
|
|
648
|
+
system: settings.values.foundry.system,
|
|
649
|
+
packaging: foundryPackaging,
|
|
650
|
+
title: opts.vaultName,
|
|
651
|
+
assets: Object.fromEntries(assetHashes),
|
|
652
|
+
});
|
|
653
|
+
for (const warning of grafts.warnings)
|
|
654
|
+
console.warn(` warning: ${warning}`);
|
|
655
|
+
// A second body per page, with links resolved to UUIDs and media pointed
|
|
656
|
+
// at markers the provider fills in. Foundry gets this one; the wiki keeps
|
|
657
|
+
// the plain `.body.html`, since the same HTML cannot serve both.
|
|
658
|
+
// A player-visible page's body carries both renders: the GM's inside a
|
|
659
|
+
// secret section, the player's in the open (see dualVariantBody). Roles
|
|
660
|
+
// build lowest first, so the player variant's body is already on disk.
|
|
661
|
+
const playerRole = settings.values.foundry.player_role;
|
|
662
|
+
const ceiling = roles.indexOf(playerRole);
|
|
663
|
+
const playerDir = collapseToRoot
|
|
664
|
+
? opts.outputDir
|
|
665
|
+
: join(opts.outputDir, "_variants", playerRole);
|
|
666
|
+
// Only bodies an entry references: a page kept out of Foundry, or one
|
|
667
|
+
// whose prose goes nowhere, must not move the hash and prompt a rebuild.
|
|
668
|
+
const referenced = new Set([...JSON.stringify(grafts.file.entries).matchAll(/@vaults\/[^/"]+\/((?:[^"\\]|\\.)+?)\.foundry\.html/g)]
|
|
669
|
+
.map((m) => JSON.parse(`"${m[1]}"`) + ".md"));
|
|
670
|
+
const bodyHashes = new Map();
|
|
671
|
+
await pMap(graftPages.filter((p) => referenced.has(p.path)), concurrency, async (page) => {
|
|
672
|
+
const base = page.path.replace(/\.md$/i, "");
|
|
673
|
+
const body = await readFile(join(variantDir, `${base}.body.html`), "utf8");
|
|
674
|
+
const gm = toFoundryHtml(body, grafts.links, role);
|
|
675
|
+
const observable = ceiling >= 0 && roles.indexOf(page.role) <= ceiling;
|
|
676
|
+
const dual = observable && roles.indexOf(role) > ceiling;
|
|
677
|
+
const out = dual
|
|
678
|
+
? dualVariantBody(gm, toFoundryHtml(await readFile(join(playerDir, `${base}.body.html`), "utf8"), grafts.links, playerRole))
|
|
679
|
+
: gm;
|
|
680
|
+
await writeFile(join(variantDir, `${base}.foundry.html`), out);
|
|
681
|
+
bodyHashes.set(page.path, createHash("md5").update(out).digest("hex"));
|
|
682
|
+
});
|
|
683
|
+
grafts.file.contentHash = contentHash(grafts.file.entries, grafts.file.assets ?? {}, bodyHashes);
|
|
684
|
+
await mkdir(join(variantDir, "_foundry"), { recursive: true });
|
|
685
|
+
await writeFile(join(variantDir, "_foundry", "grafts.json"), JSON.stringify(grafts.file, null, 2));
|
|
686
|
+
// The freshness signal, apart from the 1MB+ file it describes: the
|
|
687
|
+
// module reads this on world load to ask "anything new since I built?".
|
|
688
|
+
await writeFile(join(variantDir, "_foundry", "version.json"), JSON.stringify({ content: grafts.file.contentHash }));
|
|
604
689
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
690
|
+
}
|
|
691
|
+
// The module a reader installs, served by the vault itself: no release, no
|
|
692
|
+
// zip, no registry — the GM sends players a link to their own vault. It is
|
|
693
|
+
// generated once and then inert, so pushing content never reinstalls it.
|
|
694
|
+
if (foundryEnabled && opts.siteUrl) {
|
|
695
|
+
const dir = join(opts.outputDir, "_foundry");
|
|
696
|
+
await mkdir(dir, { recursive: true });
|
|
697
|
+
// Versioned from the manifest itself, so it reads as a date and moves only
|
|
698
|
+
// when the module changes. `assetVersion` is a hash of the stylesheet: it
|
|
699
|
+
// cannot be ordered, which is what Foundry needs to offer an update.
|
|
700
|
+
const manifest = moduleManifest({
|
|
701
|
+
moduleId: foundryModuleId,
|
|
702
|
+
title: opts.vaultName,
|
|
703
|
+
vaultUrl: opts.siteUrl,
|
|
704
|
+
systemId: settings.values.foundry.system,
|
|
705
|
+
packaging: foundryPackaging,
|
|
706
|
+
extra: settings.values.foundry.module,
|
|
707
|
+
});
|
|
708
|
+
const stamped = moduleVersion(manifest, cfg.foundryModule);
|
|
709
|
+
manifest["version"] = stamped.version;
|
|
710
|
+
if (stamped.version !== cfg.foundryModule?.version) {
|
|
711
|
+
await saveConfig(input.vaultPath, { ...cfg, foundryModule: stamped });
|
|
712
|
+
}
|
|
713
|
+
await writeFile(join(dir, "module.json"), JSON.stringify(manifest, null, 2));
|
|
714
|
+
// The module itself, which is what Foundry installs from the manifest. It
|
|
715
|
+
// holds no content: a manifest, and the one line naming the vault to read.
|
|
716
|
+
//
|
|
717
|
+
// The marker exists only inside the archive. graft reads a module's entry
|
|
718
|
+
// file from `modules/<id>/grafts.json` — the installed copy — so serving
|
|
719
|
+
// one at the deploy root would be a file nothing fetches, sharing a name
|
|
720
|
+
// with the per-variant entry list the middleware rewrites `/_foundry/` to.
|
|
721
|
+
// Pack directories are absent on purpose: Foundry creates them.
|
|
722
|
+
const manifestJson = await readFile(join(dir, "module.json"));
|
|
723
|
+
const marker = Buffer.from(JSON.stringify(moduleGrafts(opts.siteUrl, !collapseToRoot), null, 2) + "\n");
|
|
724
|
+
await writeFile(join(dir, "module.zip"), zip([
|
|
725
|
+
{ name: `${foundryModuleId}/module.json`, data: manifestJson },
|
|
726
|
+
{ name: `${foundryModuleId}/grafts.json`, data: marker },
|
|
727
|
+
]));
|
|
617
728
|
}
|
|
618
729
|
// ── Pages Functions ─────────────────────────────────────────────────────
|
|
619
730
|
// Auth middleware ships only for multi-role builds. Single-role deploys
|
|
@@ -796,7 +907,6 @@ async function buildVariant(a) {
|
|
|
796
907
|
const previewMode = previewModeOf(a.settings.preview_mode);
|
|
797
908
|
const previewModeMobile = previewModeOf(a.settings.preview_mode_mobile);
|
|
798
909
|
const previewsEnabled = previewMode !== "none" || previewModeMobile !== "none";
|
|
799
|
-
const bodyMeta = new Map();
|
|
800
910
|
await pMap(visibleMetas, a.concurrency, async (p) => {
|
|
801
911
|
const r = rendered.get(p.path);
|
|
802
912
|
const backlinkPaths = backlinkMap.get(p.path) ?? new Set();
|
|
@@ -836,7 +946,6 @@ async function buildVariant(a) {
|
|
|
836
946
|
// remark/rehype pipeline land in journals as-is, no client-side render.
|
|
837
947
|
const bodyPath = outputBase + ".body.html";
|
|
838
948
|
await writeFile(join(a.variantDir, bodyPath), r.html);
|
|
839
|
-
bodyMeta.set(bodyPath, await collectBodyMeta(p, a.vaultPath));
|
|
840
949
|
// Preview JSON feeds the popover; skip it only when neither device class
|
|
841
950
|
// shows previews (both modes "none").
|
|
842
951
|
if (previewsEnabled) {
|
|
@@ -885,17 +994,17 @@ async function buildVariant(a) {
|
|
|
885
994
|
// under the variants that need them so guessing a DM-only image URL on
|
|
886
995
|
// the public wiki structurally 404s. coverImage feeds in here too so
|
|
887
996
|
// images named via `image:` frontmatter (no body embed) still ship.
|
|
888
|
-
await copyReferencedImages(visibleSources, visibleMetas, a.imageIndex, a.imageStagingDir, a.variantDir);
|
|
997
|
+
const copiedImages = await copyReferencedImages(visibleSources, visibleMetas, a.imageIndex, a.imageStagingDir, a.variantDir);
|
|
889
998
|
// Passthrough files (audio/video/pdf/epub) follow the same gating
|
|
890
999
|
// contract as images: ship only into variants whose visible pages
|
|
891
1000
|
// reference the file. A DM-only audio cue can't ride along into the
|
|
892
1001
|
// public deploy because no public-tier source mentions it.
|
|
893
|
-
await copyReferencedPassthroughs(visibleSources, visibleMetas, a.passthroughIndex, a.passthroughStagingDir, a.variantDir, a.manifestDownloads);
|
|
1002
|
+
const copiedOther = await copyReferencedPassthroughs(visibleSources, visibleMetas, a.passthroughIndex, a.passthroughStagingDir, a.variantDir, a.manifestDownloads);
|
|
894
1003
|
return {
|
|
895
1004
|
pageCount: visibleMetas.length,
|
|
896
1005
|
pagePaths: visibleMetas.map((m) => m.path),
|
|
897
|
-
bodyMeta,
|
|
898
1006
|
hasMath: hasMathCss,
|
|
1007
|
+
assetPaths: [...copiedImages, ...copiedOther],
|
|
899
1008
|
};
|
|
900
1009
|
}
|
|
901
1010
|
/**
|
|
@@ -926,20 +1035,6 @@ async function copyKatexAssets(destDir) {
|
|
|
926
1035
|
}
|
|
927
1036
|
}
|
|
928
1037
|
}
|
|
929
|
-
/**
|
|
930
|
-
* Build the per-body manifest meta from a page's frontmatter + resolved
|
|
931
|
-
* cover image. `role` always lands so the Foundry side can apply the
|
|
932
|
-
* dmRole permission gate; the foundry / image fields are conditional.
|
|
933
|
-
*
|
|
934
|
-
* Frontmatter shape forwarded to clients:
|
|
935
|
-
* foundry:
|
|
936
|
-
* base: <UUID> | <Type>[:<subtype>] # required for instantiation
|
|
937
|
-
* sync: false # default true; skip Foundry entirely
|
|
938
|
-
* journal: false # default true; doc only, no journal page
|
|
939
|
-
* link: doc # default journal; link to the doc instead
|
|
940
|
-
* embed: false # default true
|
|
941
|
-
* data: { … deep-merged into the doc }
|
|
942
|
-
*/
|
|
943
1038
|
/** Coerce settings.theme to the layout's narrowed union, defaulting to
|
|
944
1039
|
* "auto" for any unrecognised value rather than failing the build. */
|
|
945
1040
|
function themeOf(s) {
|
|
@@ -1277,12 +1372,6 @@ function kindLabel(kind) {
|
|
|
1277
1372
|
default: return kind;
|
|
1278
1373
|
}
|
|
1279
1374
|
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Per-page extension on .body.html manifest entries, consumed by the Foundry
|
|
1282
|
-
* sync. Always carries the page's role (so the Foundry side can map roles to
|
|
1283
|
-
* JournalEntry ownership against a per-vault dmRole setting); other fields
|
|
1284
|
-
* are present only when the corresponding frontmatter is set.
|
|
1285
|
-
*/
|
|
1286
1375
|
/**
|
|
1287
1376
|
* Strip an HTML body to plain text. Used to feed the search index from
|
|
1288
1377
|
* the rendered article (post-wikilink, post-callout-redaction) so search
|