feat: implement build script and parsers to automate Foundry VTT journal pack generation from Obsidian markdown files

This commit is contained in:
Octoturge
2026-06-10 02:16:33 +02:00
parent 23af71e45a
commit 1e8f69701e
9 changed files with 42 additions and 38 deletions
+12 -12
View File
@@ -1,25 +1,25 @@
# Changelog hbm-rpg-v3-lore # Changelog - hbm-rpg-v3-lore
All notable changes to the **HbM RPG v3 Lore** Foundry VTT module. All notable changes to the **HbM RPG v3 - Lore** Foundry VTT module.
## [1.1.0] 2026-04-30 ## [1.1.0] - 2026-04-30
Version bump to track the companion system release. No content changes. Version bump to track the companion system release. No content changes.
## [1.0.0] 2026-04-29 ## [1.0.0] - 2026-04-29
First production release. First production release.
### Added ### Added
- 7 `JournalEntry` compendia generated from `_books/*.md` (36 entries total): - 7 `JournalEntry` compendia generated from `_books/*.md` (36 entries total):
- `core-rules-lore` Podręcznik Gry (5) - `core-rules-lore` - Podręcznik Gry (5)
- `magic-book-lore` Księga Magii (7) - `magic-book-lore` - Księga Magii (7)
- `bestiary-lore` Bestiariusz (1) - `bestiary-lore` - Bestiariusz (1)
- `blood-magic-history` Arcanum Sanguinis (4) - `blood-magic-history` - Arcanum Sanguinis (4)
- `crimson-cult` Chwała Szkarłatnemu Kultowi (5) - `crimson-cult` - Chwała Szkarłatnemu Kultowi (5)
- `abyss` Klątwa Otchłani (5) - `abyss` - Klątwa Otchłani (5)
- `economy` Złoto, Stal i Magia (9) - `economy` - Złoto, Stal i Magia (9)
- Build pipeline: `scripts/parsers/journal-parser.ts` walks - Build pipeline: `scripts/parsers/journal-parser.ts` walks
`Rozdział [IVX]+ [:.\-] Title`, deduplicates TOC vs. body, splits chapters by `Rozdział [IVX]+ [:.\-] Title`, deduplicates TOC vs. body, splits chapters by
`____` separators into pages, converts markdown → HTML `____` separators into pages, converts markdown → HTML
@@ -33,7 +33,7 @@ First production release.
### Deferred ### Deferred
- Przewodnik Ludzkości po Magicznym Świecie packs (`world-overview`, `deities`, - Przewodnik Ludzkości po Magicznym Świecie packs (`world-overview`, `deities`,
`universe`, `sol3-society`, `afterlife`, `documents`) book not yet in `universe`, `sol3-society`, `afterlife`, `documents`) - book not yet in
`_books/`. Will land in v1.1.0. `_books/`. Will land in v1.1.0.
- Automatic UUID cross-link injection (parser emits `lore-link` placeholders - Automatic UUID cross-link injection (parser emits `lore-link` placeholders
ready for a follow-up rewrite pass). ready for a follow-up rewrite pass).
+1 -1
View File
@@ -1,4 +1,4 @@
# HbM RPG v3 Lore (Foundry VTT module) # HbM RPG v3 - Lore (Foundry VTT module)
Companion lore module for the [HbM RPG v3 system](https://github.com/iwo-strzebonski/hbm-foundry-system). Ships JournalEntry compendia for setting, history, and narrative chapters from the official books. Companion lore module for the [HbM RPG v3 system](https://github.com/iwo-strzebonski/hbm-foundry-system). Ships JournalEntry compendia for setting, history, and narrative chapters from the official books.
+3 -1
View File
@@ -1,5 +1,7 @@
{ {
"HBM_LORE": { "HBM_LORE": {
"module": { "name": "Homebrew Magic: RPG v3 — Lore" } "module": {
"name": "Homebrew Magic: RPG v3 - Lore"
}
} }
} }
+3 -1
View File
@@ -1,5 +1,7 @@
{ {
"HBM_LORE": { "HBM_LORE": {
"module": { "name": "Homebrew Magic: RPG v3 — Lore" } "module": {
"name": "Homebrew Magic: RPG v3 - Lore"
}
} }
} }
+9 -9
View File
@@ -1,10 +1,10 @@
{ {
"id": "hbm-rpg-v3-lore", "id": "hbm-rpg-v3-lore",
"title": "Homebrew Magic: RPG v3 Lore", "title": "Homebrew Magic: RPG v3 - Lore",
"description": "Companion lore module for the HbM RPG v3 system. Provides journal entries for setting, deities, history, and narrative chapters from the official books.", "description": "Companion lore module for the HbM RPG v3 system. Provides journal entries for setting, deities, history, and narrative chapters from the official books.",
"version": "1.3.4", "version": "1.3.6",
"manifest": "https://vtt-content.octoturge.com/packages/module/hbm-rpg-v3-lore.json", "manifest": "https://vtt-content.octoturge.com/packages/module/hbm-rpg-v3-lore.json",
"download": "https://vtt-content.octoturge.com/packages/module/hbm-rpg-v3-lore-v1.3.4.zip", "download": "https://vtt-content.octoturge.com/packages/module/hbm-rpg-v3-lore-v1.3.6.zip",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "14" "verified": "14"
@@ -73,7 +73,7 @@
}, },
{ {
"name": "blood-magic-history", "name": "blood-magic-history",
"label": "Magia Krwi Historia", "label": "Magia Krwi - Historia",
"path": "packs/blood-magic-history", "path": "packs/blood-magic-history",
"type": "JournalEntry", "type": "JournalEntry",
"ownership": { "ownership": {
@@ -105,7 +105,7 @@
}, },
{ {
"name": "magic-book-lore", "name": "magic-book-lore",
"label": "Księga Magii Wprowadzenia", "label": "Księga Magii - Wprowadzenia",
"path": "packs/magic-book-lore", "path": "packs/magic-book-lore",
"type": "JournalEntry", "type": "JournalEntry",
"ownership": { "ownership": {
@@ -121,7 +121,7 @@
}, },
{ {
"name": "core-rules-lore", "name": "core-rules-lore",
"label": "Podręcznik Gry Wprowadzenia", "label": "Podręcznik Gry - Wprowadzenia",
"path": "packs/core-rules-lore", "path": "packs/core-rules-lore",
"type": "JournalEntry", "type": "JournalEntry",
"ownership": { "ownership": {
@@ -137,7 +137,7 @@
}, },
{ {
"name": "bestiary-lore", "name": "bestiary-lore",
"label": "Bestiariusz Lore", "label": "Bestiariusz - Lore",
"path": "packs/bestiary-lore", "path": "packs/bestiary-lore",
"type": "JournalEntry", "type": "JournalEntry",
"ownership": { "ownership": {
@@ -320,7 +320,7 @@
], ],
"packFolders": [ "packFolders": [
{ {
"name": "Lore HbM RPG v3", "name": "Lore - HbM RPG v3",
"sorting": "a", "sorting": "a",
"packs": [ "packs": [
"core-rules-lore", "core-rules-lore",
@@ -334,7 +334,7 @@
] ]
}, },
{ {
"name": "Świat HbM RPG v3", "name": "Świat - HbM RPG v3",
"sorting": "a", "sorting": "a",
"packs": [ "packs": [
"disciplines-lore", "disciplines-lore",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "hbm-rpg-v3-lore", "name": "hbm-rpg-v3-lore",
"version": "1.3.4", "version": "1.3.6",
"description": "Foundry VTT lore companion module for Homebrew Magic: RPG v3.", "description": "Foundry VTT lore companion module for Homebrew Magic: RPG v3.",
"private": true, "private": true,
"type": "module", "type": "module",
+2 -2
View File
@@ -83,7 +83,7 @@ const PACK_CONFIGS: PackConfig[] = [
sources: [ sources: [
// Pure lore narrative. // Pure lore narrative.
{ path: 'ObsidianNotes/lore/02. Otchłań i Magia.md' }, { path: 'ObsidianNotes/lore/02. Otchłań i Magia.md' },
// Rules intro (Wstęp only mechanics chapters are skipped). // Rules intro (Wstęp only - mechanics chapters are skipped).
{ path: 'ObsidianNotes/rules/02. Klątwa Otchłani.md', prefaceTitle: 'Wstęp' }, { path: 'ObsidianNotes/rules/02. Klątwa Otchłani.md', prefaceTitle: 'Wstęp' },
], ],
skip: /^(Rozdział II - Talenty|Rozdział III - Dary|Rozdział IV - Choroby|Rozdział V - Zaklęcia|Rozdział X - Artefakty)/i, skip: /^(Rozdział II - Talenty|Rozdział III - Dary|Rozdział IV - Choroby|Rozdział V - Zaklęcia|Rozdział X - Artefakty)/i,
@@ -238,7 +238,7 @@ for (const pack of byPack.keys()) {
console.log(` ✓ compiled ${pack}`); console.log(` ✓ compiled ${pack}`);
} }
console.log(`[lore] done ${byPack.size} packs in packs/`); console.log(`[lore] done - ${byPack.size} packs in packs/`);
function toFoundryJournal(doc: JournalDoc, sort: number): Record<string, unknown> { function toFoundryJournal(doc: JournalDoc, sort: number): Record<string, unknown> {
const fId = makeFoundryId(doc.id); const fId = makeFoundryId(doc.id);
+2 -2
View File
@@ -4,7 +4,7 @@
* `@UUID[Compendium.<pkg>.<pack>...]` patterns, and reports any reference * `@UUID[Compendium.<pkg>.<pack>...]` patterns, and reports any reference
* to a system pack we know about but a slug that does not exist. * to a system pack we know about but a slug that does not exist.
* *
* Currently informational only the journal parser does not yet emit * Currently informational only - the journal parser does not yet emit
* UUID cross-links automatically. This script provides the framework * UUID cross-links automatically. This script provides the framework
* for Phase 7.3/7.4 integration. * for Phase 7.3/7.4 integration.
*/ */
@@ -45,7 +45,7 @@ function scanFile(file: string) {
try { try {
walk(packsSrcDir); walk(packsSrcDir);
} catch { } catch {
console.log('[lint-uuid] no packs-src/ run build:packs first.'); console.log('[lint-uuid] no packs-src/ - run build:packs first.');
process.exit(0); process.exit(0);
} }
+7 -7
View File
@@ -7,7 +7,7 @@
* *
* A. Legacy Google-Docs export (`_books/`): * A. Legacy Google-Docs export (`_books/`):
* - No frontmatter. * - No frontmatter.
* - TOC is a plain-text block that reuses the same `Rozdział X Title` * - TOC is a plain-text block that reuses the same `Rozdział X - Title`
* headings. The parser deduplicates: the SECOND occurrence of each * headings. The parser deduplicates: the SECOND occurrence of each
* heading is the real body. * heading is the real body.
* *
@@ -33,7 +33,7 @@ export interface JournalDoc {
pack: string; pack: string;
/** Source provenance for traceability. */ /** Source provenance for traceability. */
source: { book: string; chapter: string; line: number }; source: { book: string; chapter: string; line: number };
/** Pages each becomes a `JournalEntryPage`. */ /** Pages - each becomes a `JournalEntryPage`. */
pages: JournalPage[]; pages: JournalPage[];
} }
@@ -66,10 +66,10 @@ export interface JournalParseConfig {
// Matches both plain-text (`Rozdział IV - Title`) and heading-prefixed // Matches both plain-text (`Rozdział IV - Title`) and heading-prefixed
// (`## Rozdział IV - Title`) chapter lines. Optional trailing page number. // (`## Rozdział IV - Title`) chapter lines. Optional trailing page number.
const CHAPTER_RE = /^(?:#{1,6}\s+)?Rozdzia[łl]\s+([IVXLC]+)\s*[:.\-]\s*(.+?)(?:\s+\d+)?$/; const CHAPTER_RE = /^(?:#{1,6}\s+)?Rozdzia[łl]\s+([IVXLC]+)\s*[:.\-]\s*(.+?)(?:\s+\d+)?$/;
const SEPARATOR = /^_{3,}$/; const SEPARATOR = /^_{3,}$/;
const FRONTMATTER_FENCE = /^---\s*$/; const FRONTMATTER_FENCE = /^---\s*$/;
// Obsidian TOC bullets: `- [[#Anchor|Label]]` skip in body content too // Obsidian TOC bullets: `- [[#Anchor|Label]]` - skip in body content too
const OBSIDIAN_TOC_BULLET = /^[-*]\s+\[\[#/; const OBSIDIAN_TOC_BULLET = /^[-*]\s+\[\[#/;
/** Returns the first non-frontmatter line index (skips `--- ... ---` block). */ /** Returns the first non-frontmatter line index (skips `--- ... ---` block). */
@@ -94,7 +94,7 @@ export function parseJournalBook(cfg: JournalParseConfig): JournalDoc[] {
if (m) occurrences.push({ idx: i, roman: m[1], title: m[2].trim(), raw: lines[i].trim() }); if (m) occurrences.push({ idx: i, roman: m[1], title: m[2].trim(), raw: lines[i].trim() });
} }
if (occurrences.length === 0) { if (occurrences.length === 0) {
// No chapters found if there's a prefaceTitle, still try to emit preface. // No chapters found - if there's a prefaceTitle, still try to emit preface.
if (cfg.prefaceTitle) { if (cfg.prefaceTitle) {
const pages = bodyToPages(lines); const pages = bodyToPages(lines);
if (pages.length > 0) { if (pages.length > 0) {
@@ -117,7 +117,7 @@ export function parseJournalBook(cfg: JournalParseConfig): JournalDoc[] {
} }
bodyChapters.push(occ); bodyChapters.push(occ);
} }
// If no duplicates were found (no plain-text TOC typical of native files), // If no duplicates were found (no plain-text TOC - typical of native files),
// treat the entire list as body chapters. // treat the entire list as body chapters.
const chapters = bodyChapters.length > 0 ? bodyChapters : occurrences; const chapters = bodyChapters.length > 0 ? bodyChapters : occurrences;
@@ -158,7 +158,7 @@ export function parseJournalBook(cfg: JournalParseConfig): JournalDoc[] {
const slug = slugify(`${ch.roman}-${ch.title}`); const slug = slugify(`${ch.roman}-${ch.title}`);
docs.push({ docs.push({
id: `${cfg.pack}-${slug}`, id: `${cfg.pack}-${slug}`,
name: `Rozdział ${ch.roman} ${ch.title}`, name: `Rozdział ${ch.roman} - ${ch.title}`,
pack: cfg.pack, pack: cfg.pack,
source: { book: cfg.sourceBook, chapter: ch.title, line: startIdx + ch.idx + 1 }, source: { book: cfg.sourceBook, chapter: ch.title, line: startIdx + ch.idx + 1 },
pages, pages,