@wizzlethorpe/vaults 0.13.2 → 0.14.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.
Files changed (53) hide show
  1. package/dist/asset-refs.js +274 -0
  2. package/dist/asset-refs.js.map +1 -0
  3. package/dist/auth.js.map +1 -1
  4. package/dist/build.js +252 -492
  5. package/dist/build.js.map +1 -1
  6. package/dist/commands/build.js +54 -5
  7. package/dist/commands/build.js.map +1 -1
  8. package/dist/commands/preview.js +0 -4
  9. package/dist/commands/preview.js.map +1 -1
  10. package/dist/commands/push.js +4 -9
  11. package/dist/commands/push.js.map +1 -1
  12. package/dist/commands/role.js +55 -14
  13. package/dist/commands/role.js.map +1 -1
  14. package/dist/config.js +25 -5
  15. package/dist/config.js.map +1 -1
  16. package/dist/foundry-importer.bundle.js +1228 -327
  17. package/dist/foundry-importer.js +2 -7
  18. package/dist/foundry-importer.js.map +1 -1
  19. package/dist/foundry-meta.js +284 -0
  20. package/dist/foundry-meta.js.map +1 -0
  21. package/dist/foundry-module-journal.js +112 -0
  22. package/dist/foundry-module-journal.js.map +1 -0
  23. package/dist/foundry-module-render.js +75 -0
  24. package/dist/foundry-module-render.js.map +1 -0
  25. package/dist/foundry-module.js +1075 -0
  26. package/dist/foundry-module.js.map +1 -0
  27. package/dist/frontmatter-defaults.js +68 -0
  28. package/dist/frontmatter-defaults.js.map +1 -0
  29. package/dist/index.js +9 -9
  30. package/dist/index.js.map +1 -1
  31. package/dist/manifest.js +115 -0
  32. package/dist/manifest.js.map +1 -0
  33. package/dist/render/auth-template.js +323 -35
  34. package/dist/render/auth-template.js.map +1 -1
  35. package/dist/render/bases.js +22 -38
  36. package/dist/render/bases.js.map +1 -1
  37. package/dist/render/cover.js +23 -1
  38. package/dist/render/cover.js.map +1 -1
  39. package/dist/render/handlers/builtin/download.js +90 -0
  40. package/dist/render/handlers/builtin/download.js.map +1 -0
  41. package/dist/render/handlers/builtin/foundry-manifest.js +158 -0
  42. package/dist/render/handlers/builtin/foundry-manifest.js.map +1 -0
  43. package/dist/render/handlers/builtin/index.js +3 -1
  44. package/dist/render/handlers/builtin/index.js.map +1 -1
  45. package/dist/render/pipeline.js +4 -1
  46. package/dist/render/pipeline.js.map +1 -1
  47. package/dist/render/slug.js +0 -5
  48. package/dist/render/slug.js.map +1 -1
  49. package/dist/scan.js +8 -0
  50. package/dist/scan.js.map +1 -1
  51. package/dist/settings.js +112 -6
  52. package/dist/settings.js.map +1 -1
  53. package/package.json +2 -1
@@ -1,17 +1,14 @@
1
1
  // Build helper: the Foundry-side importer is pre-bundled at CLI build time
2
2
  // (see scripts/bundle-importer.mjs) into `dist/foundry-importer.bundle.js`,
3
3
  // which ships inside the npm package. At deploy time we copy that bundle to
4
- // `_foundry/importer.js` and emit a `_foundry/version.json` sidecar carrying
5
- // `{ version, sha256 }` so the Foundry host can detect skew before evaluating.
4
+ // `_foundry/importer.js`, which the Foundry host fetches and evaluates.
6
5
  //
7
6
  // Bundling ahead of publish (not here) means the installed CLI never runs
8
7
  // esbuild on the user's machine and doesn't need the foundry/ source tree,
9
8
  // which isn't part of the npm package.
10
- import { createHash } from "node:crypto";
11
9
  import { mkdir, readFile, writeFile } from "node:fs/promises";
12
10
  import { basename, dirname, join } from "node:path";
13
11
  import { fileURLToPath } from "node:url";
14
- import { CLI_VERSION } from "./version.js";
15
12
  // scripts/bundle-importer.mjs always emits the bundle into dist/. This module
16
13
  // runs from dist/ once published, but from src/ under tsx (tests) — resolve to
17
14
  // dist/ either way.
@@ -19,17 +16,15 @@ const here = dirname(fileURLToPath(import.meta.url));
19
16
  const distDir = basename(here) === "src" ? join(here, "..", "dist") : here;
20
17
  const BUNDLE_PATH = join(distDir, "foundry-importer.bundle.js");
21
18
  /**
22
- * Write `_foundry/importer.js` + `_foundry/version.json` into the deploy.
19
+ * Write `_foundry/importer.js` into the deploy.
23
20
  * Called from build.ts after the variant outputs are in place — the
24
21
  * bundle is a shared root-level asset (not per-variant), since it has
25
22
  * no role-gated content.
26
23
  */
27
24
  export async function writeFoundryImporter(outputDir) {
28
25
  const source = await readFile(BUNDLE_PATH, "utf8");
29
- const sha256 = createHash("sha256").update(source).digest("hex");
30
26
  const dir = join(outputDir, "_foundry");
31
27
  await mkdir(dir, { recursive: true });
32
28
  await writeFile(join(dir, "importer.js"), source);
33
- await writeFile(join(dir, "version.json"), JSON.stringify({ version: CLI_VERSION, sha256 }, null, 2));
34
29
  }
35
30
  //# sourceMappingURL=foundry-importer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"foundry-importer.js","sourceRoot":"","sources":["../src/foundry-importer.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,uCAAuC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,8EAA8E;AAC9E,+EAA+E;AAC/E,oBAAoB;AACpB,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,SAAiB;IAC1D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACxC,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,CACb,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EACzB,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAC1D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"foundry-importer.js","sourceRoot":"","sources":["../src/foundry-importer.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,uCAAuC;AAEvC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,+EAA+E;AAC/E,oBAAoB;AACpB,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,SAAiB;IAC1D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACxC,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,284 @@
1
+ // Everything the CLI knows about the `foundry:` frontmatter block.
2
+ //
3
+ // Kept in one file because this is a shared contract, not a private detail:
4
+ // foundry/scripts/ reads exactly these keys back out of the manifest, and the
5
+ // two sides disagreeing is how `base: actor:npc` once created an Actor while
6
+ // every inbound wikilink addressed a nonexistent "actor". The doc-type rule
7
+ // here is held to foundry/scripts/foundry-base.mjs by
8
+ // cli/test/foundry-base-conformance.test.ts.
9
+ import { readFile } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ /**
12
+ * Whether a `foundry.base` will actually produce a document.
13
+ *
14
+ * Both forms do, for every type vaults can instantiate. This used to be
15
+ * narrower — the module cloned from a UUID only for Actor and Item, so a lone
16
+ * `Compendium.<pkg>.<pack>.Scene.<id>` created nothing and warning about a
17
+ * collision would have described documents that never exist. That restriction
18
+ * is gone (map packs ship their content as compendium Scenes, and those were
19
+ * all being skipped), so a well-formed base always produces a document and
20
+ * the only question left is whether the type is one we recognise.
21
+ *
22
+ * All entries name the same type by the time this runs, so `docType` answers
23
+ * for the whole list.
24
+ */
25
+ function willInstantiate(_specs, docType) {
26
+ return canonicalFoundryType(docType) !== null;
27
+ }
28
+ export function warnFoundryDocCollisions(pages) {
29
+ const seen = new Map(); // key → first page path
30
+ for (const p of pages) {
31
+ const fo = p.frontmatter?.["foundry"];
32
+ if (!fo || typeof fo !== "object" || Array.isArray(fo))
33
+ continue;
34
+ const base = fo["base"];
35
+ const specs = (Array.isArray(base) ? base : [base])
36
+ .filter((x) => typeof x === "string" && x.length > 0);
37
+ if (specs.length === 0)
38
+ continue;
39
+ const docType = foundryBaseDocName(specs[0]);
40
+ if (!docType)
41
+ continue;
42
+ if (!willInstantiate(specs, docType))
43
+ continue;
44
+ const override = fo["folder"];
45
+ const folder = typeof override === "string" && override.trim()
46
+ ? override.trim().replace(/^\/+|\/+$/g, "")
47
+ : p.path.split("/").slice(0, -1).join("/");
48
+ const name = p.title || p.path.split("/").pop().replace(/\.md$/i, "");
49
+ const key = `${docType}\u0000${folder}\u0000${name}`;
50
+ const previous = seen.get(key);
51
+ if (previous === undefined) {
52
+ seen.set(key, p.path);
53
+ continue;
54
+ }
55
+ console.warn(` ${p.path}: would create a ${docType} named '${name}' in the same Foundry `
56
+ + `folder ('${folder || "(vault root)"}') as '${previous}'. Rename one, or `
57
+ + `separate them with foundry.folder.`);
58
+ }
59
+ }
60
+ /**
61
+ * The document type a `foundry.base` spec names, read off the string without
62
+ * resolving anything. Every UUID form puts the type second-to-last
63
+ * (`Actor.<id>`, `Compendium.<pkg>.<pack>.Actor.<id>`), and a blank-doc spec
64
+ * (`Actor:npc`) carries it outright. `links.mjs` derives it the same way, so
65
+ * both sides agree on where a wikilink to the page points.
66
+ */
67
+ export function foundryBaseDocName(spec) {
68
+ // A Moulinette reference names no type. Checked before the UUID rule
69
+ // because its file segment ends in ".json", which the dot test would
70
+ // otherwise read as a UUID. The type comes from another entry in the list;
71
+ // normalizeFoundryBase makes sure one is there.
72
+ if (spec.startsWith("@moulinette/"))
73
+ return null;
74
+ if (spec.includes(".")) {
75
+ const parts = spec.split(".");
76
+ if (parts.length < 2)
77
+ return null;
78
+ const raw = parts[parts.length - 2];
79
+ // Unknown types pass through: vaults can't instantiate a Combat, but
80
+ // Foundry may still resolve the UUID, and reporting the type beats
81
+ // claiming the spec names none.
82
+ return canonicalFoundryType(raw) ?? raw ?? null;
83
+ }
84
+ // Blank-document form. An unrecognised type is not a base at all, so it is
85
+ // rejected rather than passed through — matching the Foundry module, which
86
+ // would create nothing for it.
87
+ return canonicalFoundryType(spec.split(":")[0]);
88
+ }
89
+ /**
90
+ * Fold a type segment to its canonical spelling, or null if vaults doesn't
91
+ * instantiate it.
92
+ *
93
+ * Case matters downstream and is hand-typed here: `base: actor:npc` is
94
+ * supported, but Foundry's `@UUID[...]` enricher does a case-sensitive
95
+ * lookup. Returning "actor" made the CLI treat it as a different type from
96
+ * "Actor" — so a list mixing the two failed the same-type check and had its
97
+ * whole foundry.base dropped. Kept in step with
98
+ * foundry/scripts/foundry-base.mjs by cli/test/foundry-base-conformance.test.ts.
99
+ */
100
+ export function canonicalFoundryType(raw) {
101
+ if (!raw)
102
+ return null;
103
+ return FOUNDRY_BLANK_DOC_TYPES.find((t) => t.toLowerCase() === raw.toLowerCase()) ?? null;
104
+ }
105
+ export const FOUNDRY_BLANK_DOC_TYPES = [
106
+ "Actor", "Item", "Scene", "JournalEntry",
107
+ "RollTable", "Macro", "Cards", "Playlist",
108
+ ];
109
+ /**
110
+ * Validate `foundry.base` and normalize it for the manifest: a single string
111
+ * stays a string (so an older Foundry module keeps working), a list of two or
112
+ * more stays a list. Returns null when the value can't be used, having said
113
+ * why — the build continues, and the page syncs as a journal with no document.
114
+ *
115
+ * Every entry must name the same document type. The module reads that type
116
+ * off the spec rather than off a resolved template, and `links.mjs` has to
117
+ * reach the same answer with no lookup at all, so a list that disagrees with
118
+ * itself has no single answer to give.
119
+ */
120
+ function normalizeFoundryBase(base, pagePath) {
121
+ const raw = Array.isArray(base) ? base : [base];
122
+ const specs = [];
123
+ for (const entry of raw) {
124
+ if (typeof entry !== "string" || entry.trim().length === 0) {
125
+ console.warn(` ${pagePath}: foundry.base entries must be non-empty strings (a UUID like `
126
+ + `"Compendium.<pkg>.<pack>.Actor.<id>", or a type like "Actor:npc"); `
127
+ + `got ${entry === null ? "null" : typeof entry}. Ignoring foundry.base — this page `
128
+ + `will sync as a journal but create no document.`);
129
+ return null;
130
+ }
131
+ specs.push(entry.trim());
132
+ }
133
+ if (specs.length === 0) {
134
+ console.warn(` ${pagePath}: foundry.base is an empty list; ignoring.`);
135
+ return null;
136
+ }
137
+ const types = new Map(); // docName → first spec that named it
138
+ for (const spec of specs) {
139
+ // A Moulinette entry is deliberately typeless: only the reader's own
140
+ // library knows whether that asset is a Scene or an Actor, and this runs
141
+ // at build time with no library to ask.
142
+ if (spec.startsWith("@moulinette/"))
143
+ continue;
144
+ const docName = foundryBaseDocName(spec);
145
+ if (!docName) {
146
+ console.warn(` ${pagePath}: foundry.base entry "${spec}" names no document type; ignoring foundry.base.`);
147
+ return null;
148
+ }
149
+ if (!types.has(docName))
150
+ types.set(docName, spec);
151
+ }
152
+ if (types.size === 0) {
153
+ console.warn(` ${pagePath}: foundry.base names only Moulinette references, which carry no document `
154
+ + `type. Add an entry naming the type (e.g. "Scene") — it is also what the page falls `
155
+ + `back to for a reader without that pack. Ignoring foundry.base.`);
156
+ return null;
157
+ }
158
+ if (types.size > 1) {
159
+ const detail = [...types].map(([t, spec]) => `${t} (from "${spec}")`).join(", ");
160
+ console.warn(` ${pagePath}: every foundry.base entry must name the same document type, got ${detail}. `
161
+ + `Ignoring foundry.base — this page will sync as a journal but create no document.`);
162
+ return null;
163
+ }
164
+ // A list whose last entry is a UUID can still fail on a world that lacks
165
+ // every package named. A blank-doc tail is what makes the chain total.
166
+ const last = specs[specs.length - 1];
167
+ if (specs.length > 1 && (last.includes(".") || last.startsWith("@moulinette/"))) {
168
+ console.warn(` ${pagePath}: foundry.base list ends with "${last}", so it can still resolve to nothing. `
169
+ + `End with a blank-document entry (e.g. "${[...types.keys()][0]}:npc" or "${[...types.keys()][0]}") `
170
+ + `to guarantee a document.`);
171
+ }
172
+ return specs.length === 1 ? specs[0] : specs;
173
+ }
174
+ export async function collectBodyMeta(p, vaultPath) {
175
+ const fm = p.frontmatter ?? {};
176
+ const out = { role: p.role };
177
+ const basename = p.path.split("/").pop().replace(/\.md$/i, "");
178
+ if (p.title && p.title !== basename)
179
+ out.title = p.title;
180
+ const fo = fm["foundry"];
181
+ if (fo && typeof fo === "object" && !Array.isArray(fo)) {
182
+ const block = {};
183
+ // `base` is one spec, or a priority list the module tries in order so a
184
+ // vault degrades across worlds with different content installed. A
185
+ // malformed base is dropped with a warning rather than failing the build,
186
+ // same as foundry.id below. Silence here is worse than it looks: the
187
+ // module never receives the key, so it can't report the page either, and
188
+ // the page syncs as a journal with no Actor/Item and no explanation.
189
+ const base = fo["base"];
190
+ if (base !== undefined && base !== null) {
191
+ const normalized = normalizeFoundryBase(base, p.path);
192
+ if (normalized !== null)
193
+ block.base = normalized;
194
+ }
195
+ const embed = fo["embed"];
196
+ if (typeof embed === "boolean")
197
+ block.embed = embed;
198
+ // foundry.sync: false keeps the page out of Foundry altogether — no
199
+ // JournalEntryPage, no derived doc. The page still renders on the wiki.
200
+ // Unlike `embed`, which only suppresses the article inside a derived
201
+ // doc's description, this drops the page from the sync set entirely.
202
+ const sync = fo["sync"];
203
+ if (typeof sync === "boolean")
204
+ block.sync = sync;
205
+ // foundry.journal: false makes the derived doc without the JournalEntryPage
206
+ // that normally accompanies it. For a page that exists to carry a Scene or
207
+ // an Actor and has no article worth reading in the sidebar.
208
+ const journal = fo["journal"];
209
+ if (typeof journal === "boolean")
210
+ block.journal = journal;
211
+ // foundry.link: "doc" makes wikilinks to this page resolve to the document
212
+ // it instantiates rather than to its journal page. Implied by
213
+ // `journal: false`, where there is no journal page to link to.
214
+ const link = fo["link"];
215
+ if (link === "doc" || link === "journal")
216
+ block.link = link;
217
+ const data = fo["data"];
218
+ if (data && typeof data === "object" && !Array.isArray(data))
219
+ block.data = data;
220
+ // foundry.folder: a "/"-separated folder path the instantiated doc is
221
+ // filed under, nested inside the vault's own sidebar folder. Absent
222
+ // means the vault folder itself, which is where everything used to land.
223
+ const folder = fo["folder"];
224
+ if (typeof folder === "string" && folder.trim().length > 0)
225
+ block.folder = folder.trim();
226
+ // foundry.id: an explicit Foundry document id for this page. When set,
227
+ // overrides the SHA1-derived id used for both the JournalEntryPage and
228
+ // (if foundry.base is present) the instantiated derived doc. Lets users
229
+ // hardcode UUIDs that other Foundry-side code (macros, scene flags,
230
+ // module integrations) needs to reference. Foundry ids are 16 chars from
231
+ // [A-Za-z0-9]; a malformed value is dropped with a warning rather than
232
+ // failing the build.
233
+ const idVal = fo["id"];
234
+ if (typeof idVal === "string") {
235
+ const trimmed = idVal.trim();
236
+ if (FOUNDRY_ID_RE.test(trimmed))
237
+ block.id = trimmed;
238
+ else if (trimmed.length > 0) {
239
+ console.warn(` ${p.path}: foundry.id "${trimmed}" is not a valid Foundry id (16 chars [A-Za-z0-9]); ignoring`);
240
+ }
241
+ }
242
+ // foundry.data_json: vault-relative path to a JSON file. Read + parse
243
+ // at build time and inline into the meta as `data_json`. The Foundry
244
+ // module deep-merges it onto the base doc BEFORE foundry.data, so a
245
+ // user can layer hand-tuned overrides on top of an exported sheet.
246
+ // Folding the parsed object into meta means the body-row hash already
247
+ // changes when the JSON content does — no separate change-detection.
248
+ const dataJsonPath = fo["data_json"];
249
+ if (typeof dataJsonPath === "string" && dataJsonPath.trim().length > 0) {
250
+ const parsed = await loadDataJson(vaultPath, dataJsonPath.trim(), p.path);
251
+ if (parsed !== null)
252
+ block.data_json = parsed;
253
+ }
254
+ if (Object.keys(block).length > 0)
255
+ out.foundry = block;
256
+ }
257
+ if (p.coverImage)
258
+ out.image = p.coverImage;
259
+ return out;
260
+ }
261
+ /** Read + parse a vault-relative JSON file referenced by `foundry.data_json`.
262
+ * Warns on missing / unparseable file and returns null so the page renders
263
+ * without the overlay rather than failing the build. */
264
+ export async function loadDataJson(vaultPath, relPath, pagePath) {
265
+ const abs = join(vaultPath, relPath);
266
+ try {
267
+ const raw = await readFile(abs, "utf8");
268
+ return JSON.parse(raw);
269
+ }
270
+ catch (err) {
271
+ const code = err.code;
272
+ if (code === "ENOENT") {
273
+ console.warn(` ${pagePath}: foundry.data_json "${relPath}" not found, skipping`);
274
+ }
275
+ else {
276
+ console.warn(` ${pagePath}: foundry.data_json "${relPath}" failed to parse: ${err.message}`);
277
+ }
278
+ return null;
279
+ }
280
+ }
281
+ /** Foundry document ids: exactly 16 chars from [A-Za-z0-9]. Validated when
282
+ * authors set `foundry.id` to override the SHA1-derived default. */
283
+ const FOUNDRY_ID_RE = /^[A-Za-z0-9]{16}$/;
284
+ //# sourceMappingURL=foundry-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundry-meta.js","sourceRoot":"","sources":["../src/foundry-meta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ // Build a module's JournalEntry pack from the pages the wiki already rendered.
2
+ //
3
+ // Mirrors the sync model exactly: one JournalEntry per vault directory, every
4
+ // .md file in that directory an embedded JournalEntryPage. Matching it is the
5
+ // point — a document's description embeds a journal page, and if the module
6
+ // laid its journals out differently, the same vault would produce two
7
+ // different-shaped things depending on how a reader got it.
8
+ //
9
+ // The HTML is the wiki's own rendered body, not a second rendering of the
10
+ // markdown. That matters: a page's battlemap, statblock and `fm:` values only
11
+ // exist in the rendered output, so re-rendering the source with a plain
12
+ // markdown parser would quietly ship a different article than the one on the
13
+ // site.
14
+ import { createHash } from "node:crypto";
15
+ /** The sync path's id scheme, namespaced to the module instead of the vault. */
16
+ function det(kind, key) {
17
+ return createHash("sha1").update(`vaults:${kind}:${key}`).digest("hex").slice(0, 16);
18
+ }
19
+ export function folderOfPath(path) {
20
+ const i = path.lastIndexOf("/");
21
+ return i < 0 ? "" : path.slice(0, i);
22
+ }
23
+ /** One entry per directory, so every page in a folder shares an id. */
24
+ export const journalEntryId = (ns, path) => det("entry", `${ns}:${folderOfPath(path)}`);
25
+ export const journalPageId = (ns, path) => det("page", `${ns}:${path}`);
26
+ /**
27
+ * Rewrite the wiki's HTML into something a Foundry journal can render.
28
+ *
29
+ * Two things have to change and the rest is already fine. An internal link is
30
+ * a site path that means nothing inside Foundry, so it becomes a `@UUID`
31
+ * enricher pointing at the compendium page. A media src is a deploy-absolute
32
+ * path, so it becomes a module-relative one and the file is bundled.
33
+ *
34
+ * Deliberately narrower than the sync path's transform, which also converts
35
+ * role-gated callouts into Foundry secret blocks. A module carries only one
36
+ * role's pages, so there is no gated content inside them to convert.
37
+ */
38
+ export function transformForModule(html, moduleId, targets, assets) {
39
+ // An internal link the renderer marked. Attribute order is not fixed — the
40
+ // renderer emits `class` before `href` in some cases and after in others —
41
+ // so match the whole tag and read the attributes out of it rather than
42
+ // assuming a shape. Anchoring on `href` first silently missed every link
43
+ // that happened to be written the other way round.
44
+ let out = html.replace(/<a\s+([^>]*)>([\s\S]*?)<\/a>/g, (whole, attrs, label) => {
45
+ if (!/\bclass="[^"]*\binternal\b/.test(attrs))
46
+ return whole;
47
+ const href = /href="([^"]*)"/.exec(attrs)?.[1];
48
+ if (!href || !href.startsWith("/"))
49
+ return whole;
50
+ const key = decodeURIComponent(href.replace(/^\//, "").split("#")[0] ?? "");
51
+ const target = targets.get(key) ?? targets.get(`${key}.md`);
52
+ if (!target)
53
+ return label; // page not in the module: keep the words, drop the dead link
54
+ // Strip tags from the label: an enricher's {…} is plain text.
55
+ const text = label.replace(/<[^>]+>/g, "").trim();
56
+ return `@UUID[${target.uuid}]{${text}}`;
57
+ });
58
+ // src="/attachments/x.webp" — deploy-absolute, meaningless in a module.
59
+ out = out.replace(/(\ssrc=")\/([^"]+)"/g, (_whole, prefix, path) => {
60
+ const rel = decodeURIComponent(path);
61
+ assets.add(rel);
62
+ return `${prefix}modules/${moduleId}/assets/${rel}"`;
63
+ });
64
+ return out;
65
+ }
66
+ /**
67
+ * Group pages into one JournalEntry per directory.
68
+ *
69
+ * The entry's name is the directory's own name, and a root-level page lands in
70
+ * an entry named for the module — the same shape the sync path produces, where
71
+ * a directory becomes an entry and its files become that entry's pages.
72
+ */
73
+ export function buildJournalEntries(sources, ns, rootName, stats) {
74
+ const byFolder = new Map();
75
+ for (const s of sources) {
76
+ const folder = folderOfPath(s.path);
77
+ const list = byFolder.get(folder) ?? [];
78
+ list.push(s);
79
+ byFolder.set(folder, list);
80
+ }
81
+ const entries = [];
82
+ for (const [folder, pages] of [...byFolder].sort(([a], [b]) => a.localeCompare(b))) {
83
+ const id = journalEntryId(ns, pages[0].path);
84
+ entries.push({
85
+ _id: id,
86
+ name: folder ? folder.split("/").pop() : rootName,
87
+ pages: pages.map((p, i) => {
88
+ const pid = journalPageId(ns, p.path);
89
+ return {
90
+ _id: pid,
91
+ name: p.title,
92
+ type: "text",
93
+ title: { show: true, level: 1 },
94
+ text: { format: 1, content: p.html },
95
+ sort: (i + 1) * 100000,
96
+ ownership: { default: -1 },
97
+ flags: {},
98
+ _stats: stats,
99
+ _key: `!journal.pages!${id}.${pid}`,
100
+ };
101
+ }),
102
+ folder: null,
103
+ sort: 0,
104
+ flags: {},
105
+ ownership: { default: 0 },
106
+ _stats: stats,
107
+ _key: `!journal!${id}`,
108
+ });
109
+ }
110
+ return entries;
111
+ }
112
+ //# sourceMappingURL=foundry-module-journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundry-module-journal.js","sourceRoot":"","sources":["../src/foundry-module-journal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ // Render a vault page's markdown body into Foundry-flavoured description HTML,
2
+ // and build a pack's compendium folders.
3
+ //
4
+ // Ported from the standalone compiler this replaced, so `vaults build --module` can
5
+ // replace it. A vault body is Obsidian/vaults markdown; a Foundry sheet wants
6
+ // HTML with native inline rolls and Compendium @UUID cross-links.
7
+ import { createHash } from "node:crypto";
8
+ import { marked } from "marked";
9
+ // Fenced blocks that are wiki chrome: the Foundry sheet renders the same
10
+ // mechanics natively, so carrying the block's source into a description would
11
+ // show the reader a code listing of something already on their screen.
12
+ const HANDLER_FENCES = new Set([
13
+ "spell-card", "item-card", "statblock", "statblock-fm", "gallery", "battlemap",
14
+ "rolltable", "dice", "download", "foundry-manifest",
15
+ ]);
16
+ function dropHandlerFences(md) {
17
+ return md.replace(/^```([\w-]+)[^\n]*\n[\s\S]*?^```[ \t]*$/gm, (block, lang) => HANDLER_FENCES.has(lang) ? "" : block);
18
+ }
19
+ /** `dice: 2d6+1` in backticks becomes dnd5e's native inline roll. */
20
+ function diceToRolls(md) {
21
+ return md.replace(/`dice:\s*([^`]+?)`/gi, (_m, f) => `[[/r ${f.trim()}]]`);
22
+ }
23
+ function rewriteWikilinks(md, index) {
24
+ return md.replace(/\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|([^\]]+))?\]\]/g, (_m, target, label) => {
25
+ const entry = index.get(target.trim().toLowerCase());
26
+ const text = (label ?? target).trim();
27
+ return entry ? `@UUID[${entry.uuid}]{${text}}` : text;
28
+ });
29
+ }
30
+ /**
31
+ * Wikilinks are rewritten before dice, not after: at that point a `dice:` span
32
+ * is still inside backticks, so the wikilink pass cannot mistake the `[[/r …]]`
33
+ * this would otherwise have produced for a link of its own.
34
+ */
35
+ export function renderBody(md, index) {
36
+ let out = dropHandlerFences(md);
37
+ out = rewriteWikilinks(out, index);
38
+ out = diceToRolls(out);
39
+ return marked.parse(out, { async: false }).trim();
40
+ }
41
+ function folderId(packName, pathStr) {
42
+ return createHash("sha1").update(`folder:${packName}:${pathStr}`).digest("base64")
43
+ .replace(/[^A-Za-z0-9]/g, "").slice(0, 16).padEnd(16, "0");
44
+ }
45
+ /**
46
+ * Build `!folders!` documents for a pack, plus the leaf folder each entry
47
+ * belongs in. `fvtt package pack` turns these keys into real compendium
48
+ * folders, so a pack of 500 items is browsable rather than one flat list.
49
+ */
50
+ export function buildFolders(entries, packName, docType, stats) {
51
+ const docs = new Map();
52
+ const leafFor = new Map();
53
+ const ensure = (segments) => {
54
+ let parent = null;
55
+ let pathStr = "";
56
+ for (const seg of segments) {
57
+ pathStr = pathStr ? `${pathStr}/${seg}` : seg;
58
+ if (!docs.has(pathStr)) {
59
+ const id = folderId(packName, pathStr);
60
+ docs.set(pathStr, {
61
+ _id: id, name: seg, type: docType, folder: parent,
62
+ sort: 0, color: null, sorting: "a", flags: {},
63
+ _stats: stats, _key: `!folders!${id}`,
64
+ });
65
+ }
66
+ parent = docs.get(pathStr)._id;
67
+ }
68
+ return parent;
69
+ };
70
+ for (const e of entries) {
71
+ leafFor.set(e.key, ensure(e.folderPath.split("/").map((s) => s.trim()).filter(Boolean)));
72
+ }
73
+ return { folderDocs: [...docs.values()], leafFor };
74
+ }
75
+ //# sourceMappingURL=foundry-module-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundry-module-render.js","sourceRoot":"","sources":["../src/foundry-module-render.ts"],"names":[],"mappings":"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"}