From 23af71e45a9321946deeab9994bde13a36f5ebe4 Mon Sep 17 00:00:00 2001 From: Octoturge Date: Tue, 9 Jun 2026 22:07:05 +0200 Subject: [PATCH] Initial commit --- .gitignore | 6 + CHANGELOG.md | 41 ++++ README.md | 25 +++ bun.lock | 313 ++++++++++++++++++++++++++ lang/en.json | 5 + lang/pl.json | 5 + module.json | 353 ++++++++++++++++++++++++++++++ package.json | 22 ++ scripts/build-journal-packs.ts | 303 +++++++++++++++++++++++++ scripts/bump-version.ts | 44 ++++ scripts/lint-uuid-refs.ts | 59 +++++ scripts/package.ts | 39 ++++ scripts/parsers/journal-parser.ts | 300 +++++++++++++++++++++++++ scripts/parsers/topical-parser.ts | 251 +++++++++++++++++++++ scripts/push-package.ts | 83 +++++++ tsconfig.json | 16 ++ 16 files changed, 1865 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 bun.lock create mode 100644 lang/en.json create mode 100644 lang/pl.json create mode 100644 module.json create mode 100644 package.json create mode 100644 scripts/build-journal-packs.ts create mode 100644 scripts/bump-version.ts create mode 100644 scripts/lint-uuid-refs.ts create mode 100644 scripts/package.ts create mode 100644 scripts/parsers/journal-parser.ts create mode 100644 scripts/parsers/topical-parser.ts create mode 100644 scripts/push-package.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea99b62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +packs-src/ +packs/ +*.zip +.DS_Store +.env diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1075e10 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog — hbm-rpg-v3-lore + +All notable changes to the **HbM RPG v3 — Lore** Foundry VTT module. + +## [1.1.0] — 2026-04-30 + +Version bump to track the companion system release. No content changes. + +## [1.0.0] — 2026-04-29 + +First production release. + +### Added + +- 7 `JournalEntry` compendia generated from `_books/*.md` (36 entries total): + - `core-rules-lore` — Podręcznik Gry (5) + - `magic-book-lore` — Księga Magii (7) + - `bestiary-lore` — Bestiariusz (1) + - `blood-magic-history` — Arcanum Sanguinis (4) + - `crimson-cult` — Chwała Szkarłatnemu Kultowi (5) + - `abyss` — Klątwa Otchłani (5) + - `economy` — Złoto, Stal i Magia (9) +- Build pipeline: `scripts/parsers/journal-parser.ts` walks + `Rozdział [IVX]+ [:.\-] Title`, deduplicates TOC vs. body, splits chapters by + `____` separators into pages, converts markdown → HTML + (paragraphs / lists / headings / bold / italic / `[[wikilink]]` → + `` for downstream UUID rewriting). +- `scripts/build-journal-packs.ts` writes per-pack JSON and compiles to LevelDB. +- `scripts/package.ts` produces `hbm-rpg-v3-lore-vX.Y.Z.zip`. +- `scripts/lint-uuid-refs.ts` framework for cross-link validation. +- Declares `relationships.systems[hbm-rpg-v3 ≥1.0.0]`. + +### Deferred + +- Przewodnik Ludzkości po Magicznym Świecie packs (`world-overview`, `deities`, + `universe`, `sol3-society`, `afterlife`, `documents`) — book not yet in + `_books/`. Will land in v1.1.0. +- Automatic UUID cross-link injection (parser emits `lore-link` placeholders + ready for a follow-up rewrite pass). +- Reverse cross-links from system NPC actors to lore journal pages. +- Asset bundling from vault `_assets/`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4efc9c2 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# 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. + +## Build + +```bash +bun install +bun run build:packs # parse _books/*.md → packs/ (LevelDB) +bun run package # zip into hbm-rpg-v3-lore-vX.Y.Z.zip +``` + +Requires the `_books/` folder at the repo root (synced via the parent CLI). + +## Packs + +| Pack | Source book | +|------|-------------| +| `core-rules-lore` | Podręcznik Gry | +| `magic-book-lore` | Księga Magii | +| `bestiary-lore` | Bestiariusz | +| `blood-magic-history` | Arcanum Sanguinis | +| `crimson-cult` | Chwała Szkarłatnemu Kultowi | +| `abyss` | Klątwa Otchłani | +| `economy` | Złoto, Stal i Magia | diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..ced89c4 --- /dev/null +++ b/bun.lock @@ -0,0 +1,313 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "hbm-rpg-v3-lore", + "devDependencies": { + "@foundryvtt/foundryvtt-cli": "^3.0.3", + "@types/archiver": "^7.0.0", + "@types/node": "^25.6.0", + "archiver": "^7.0.1", + "typescript": "^5.5.0", + }, + }, + }, + "packages": { + "@foundryvtt/foundryvtt-cli": ["@foundryvtt/foundryvtt-cli@3.0.3", "", { "dependencies": { "chalk": "^5.4.1", "classic-level": "^1.4.1", "esm": "^3.2.25", "js-yaml": "^4.1.0", "mkdirp": "^3.0.1", "nedb-promises": "^6.2.3", "yargs": "^17.7.2" }, "bin": { "fvtt": "fvtt.mjs" } }, "sha512-byNLOrZ9ev6PfW+gflhonT5Y+Goq2aHERwVGWO3eYRKez+lzZJoqMY/YMEi54XYozzWWn8d6LuRbBsVPJ30haw=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@seald-io/binary-search-tree": ["@seald-io/binary-search-tree@1.0.3", "", {}, "sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA=="], + + "@seald-io/nedb": ["@seald-io/nedb@4.1.2", "", { "dependencies": { "@seald-io/binary-search-tree": "^1.0.3", "localforage": "^1.10.0", "util": "^0.12.5" } }, "sha512-bDr6TqjBVS2rDyYM9CPxAnotj5FuNL9NF8o7h7YyFXM7yruqT4ddr+PkSb2mJvvw991bqdftazkEo38gykvaww=="], + + "@types/archiver": ["@types/archiver@7.0.0", "", { "dependencies": { "@types/readdir-glob": "*" } }, "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg=="], + + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "@types/readdir-glob": ["@types/readdir-glob@1.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + + "abstract-level": ["abstract-level@1.0.4", "", { "dependencies": { "buffer": "^6.0.3", "catering": "^2.1.0", "is-buffer": "^2.0.5", "level-supports": "^4.0.0", "level-transcoder": "^1.0.1", "module-error": "^1.0.1", "queue-microtask": "^1.2.3" } }, "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], + + "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], + + "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], + + "bare-os": ["bare-os@3.9.0", "", {}, "sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q=="], + + "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], + + "bare-stream": ["bare-stream@2.13.1", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow=="], + + "bare-url": ["bare-url@2.4.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="], + + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "catering": ["catering@2.1.1", "", {}, "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "classic-level": ["classic-level@1.4.1", "", { "dependencies": { "abstract-level": "^1.0.2", "catering": "^2.1.0", "module-error": "^1.0.1", "napi-macros": "^2.2.2", "node-gyp-build": "^4.3.0" } }, "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "esm": ["esm@3.2.25", "", {}, "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], + + "is-buffer": ["is-buffer@2.0.5", "", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + + "level-supports": ["level-supports@4.0.1", "", {}, "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA=="], + + "level-transcoder": ["level-transcoder@1.0.1", "", { "dependencies": { "buffer": "^6.0.3", "module-error": "^1.0.1" } }, "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w=="], + + "lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="], + + "localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="], + + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "module-error": ["module-error@1.0.2", "", {}, "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA=="], + + "napi-macros": ["napi-macros@2.2.2", "", {}, "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g=="], + + "nedb-promises": ["nedb-promises@6.2.3", "", { "dependencies": { "@seald-io/nedb": "^4.0.2" } }, "sha512-enq0IjNyBz9Qy9W/QPCcLGh/QORGBjXbIeZeWvIjO3OMLyAvlKT3hiJubP2BKEiFniUlR3L01o18ktqgn5jxqA=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="], + + "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + } +} diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..0cbbb95 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,5 @@ +{ + "HBM_LORE": { + "module": { "name": "Homebrew Magic: RPG v3 — Lore" } + } +} diff --git a/lang/pl.json b/lang/pl.json new file mode 100644 index 0000000..0cbbb95 --- /dev/null +++ b/lang/pl.json @@ -0,0 +1,5 @@ +{ + "HBM_LORE": { + "module": { "name": "Homebrew Magic: RPG v3 — Lore" } + } +} diff --git a/module.json b/module.json new file mode 100644 index 0000000..52ea2ff --- /dev/null +++ b/module.json @@ -0,0 +1,353 @@ +{ + "id": "hbm-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.", + "version": "1.3.4", + "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", + "compatibility": { + "minimum": "13", + "verified": "14" + }, + "relationships": { + "systems": [ + { + "id": "hbm-rpg-v3", + "type": "system", + "compatibility": { + "minimum": "1.0.0", + "verified": "1.0.0" + } + } + ] + }, + "authors": [ + { + "name": "Iwo Strzeboński" + } + ], + "languages": [ + { + "lang": "pl", + "name": "Polski", + "path": "lang/pl.json" + }, + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + } + ], + "packs": [ + { + "name": "abyss", + "label": "Otchłań", + "path": "packs/abyss", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "abyss-curse" + } + } + }, + { + "name": "crimson-cult", + "label": "Szkarłatny Kult", + "path": "packs/crimson-cult", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "crimson-cult" + } + } + }, + { + "name": "blood-magic-history", + "label": "Magia Krwi — Historia", + "path": "packs/blood-magic-history", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "arcanum-sanguinis" + } + } + }, + { + "name": "economy", + "label": "Złoto, Stal i Magia", + "path": "packs/economy", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "gold-steel-magic" + } + } + }, + { + "name": "magic-book-lore", + "label": "Księga Magii — Wprowadzenia", + "path": "packs/magic-book-lore", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "magic-book" + } + } + }, + { + "name": "core-rules-lore", + "label": "Podręcznik Gry — Wprowadzenia", + "path": "packs/core-rules-lore", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "core-rules" + } + } + }, + { + "name": "bestiary-lore", + "label": "Bestiariusz — Lore", + "path": "packs/bestiary-lore", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "bestiary" + } + } + }, + { + "name": "humanity-guide", + "label": "Przewodnik Ludzkości po Magicznym Świecie", + "path": "packs/humanity-guide", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "lore", + "sourceBook": "humanity-guide" + } + } + }, + { + "name": "disciplines-lore", + "label": "Dziedziny Magii", + "path": "packs/disciplines-lore", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "organizations", + "label": "Organizacje", + "path": "packs/organizations", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "races-lore", + "label": "Rasy", + "path": "packs/races-lore", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "classes", + "label": "Klasy (Grupy)", + "path": "packs/classes", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "concepts", + "label": "Koncepty", + "path": "packs/concepts", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "locations", + "label": "Lokacje", + "path": "packs/locations", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "npcs", + "label": "Postacie", + "path": "packs/npcs", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "conflicts", + "label": "Konflikty", + "path": "packs/conflicts", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "player-characters", + "label": "Postacie Graczy", + "path": "packs/player-characters", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + }, + { + "name": "adventures", + "label": "Przygody", + "path": "packs/adventures", + "type": "JournalEntry", + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + }, + "flags": { + "hbm-rpg-v3-lore": { + "category": "vault" + } + } + } + ], + "packFolders": [ + { + "name": "Lore — HbM RPG v3", + "sorting": "a", + "packs": [ + "core-rules-lore", + "magic-book-lore", + "bestiary-lore", + "blood-magic-history", + "crimson-cult", + "abyss", + "economy", + "humanity-guide" + ] + }, + { + "name": "Świat — HbM RPG v3", + "sorting": "a", + "packs": [ + "disciplines-lore", + "organizations", + "races-lore", + "classes", + "concepts", + "locations", + "npcs", + "conflicts", + "player-characters", + "adventures" + ] + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..36e678b --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "hbm-rpg-v3-lore", + "version": "1.3.4", + "description": "Foundry VTT lore companion module for Homebrew Magic: RPG v3.", + "private": true, + "type": "module", + "scripts": { + "build:packs": "bun scripts/build-journal-packs.ts", + "build:packs:strict": "bun scripts/build-journal-packs.ts --strict", + "lint:uuid-refs": "bun scripts/lint-uuid-refs.ts", + "package": "bun scripts/bump-version.ts && bun run build:packs && bun scripts/package.ts", + "push": "bun scripts/push-package.ts" + }, + "devDependencies": { + "@foundryvtt/foundryvtt-cli": "^3.0.3", + "@types/archiver": "^7.0.0", + "@types/node": "^25.6.0", + "archiver": "^7.0.1", + "typescript": "^5.5.0" + }, + "license": "SEE LICENSE IN LICENSE.txt" +} diff --git a/scripts/build-journal-packs.ts b/scripts/build-journal-packs.ts new file mode 100644 index 0000000..73aa31c --- /dev/null +++ b/scripts/build-journal-packs.ts @@ -0,0 +1,303 @@ +/** + * Build LevelDB JournalEntry packs from book chapters. + * + * One pack per book / topic (mapped via PACK_CONFIGS + TOPICAL_CONFIGS). + * + * Split-book packs accept multiple source files: a list of `SourceFile` + * entries each pointing at a `rules/` or `lore/` markdown file (instead of a + * single monolithic `_books/` file). An optional `prefaceTitle` per source + * captures the introductory content before the first chapter heading. + * + * Run: `bun scripts/build-journal-packs.ts` from `.src/foundry-lore/`. + */ + +import { mkdirSync, rmSync, writeFileSync, existsSync, readdirSync, statSync, readFileSync } from 'node:fs'; +import { dirname, resolve, basename, extname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { compilePack } from '@foundryvtt/foundryvtt-cli'; +import { parseJournalBook, type JournalDoc, type JournalPage } from './parsers/journal-parser'; +import { parseTopicalFolder } from './parsers/topical-parser'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const moduleRoot = resolve(__dirname, '..'); +const repoRoot = resolve(moduleRoot, '..', '..'); +const booksDir = resolve(repoRoot, '_books'); +const packsSrcDir = resolve(moduleRoot, 'packs-src'); +const packsOutDir = resolve(moduleRoot, 'packs'); + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface SourceFile { + /** Path relative to repoRoot. */ + path: string; + /** + * If set, lines before the first chapter heading are collected and emitted + * as a synthetic journal entry with this title (e.g. "Wstęp"). + */ + prefaceTitle?: string; +} + +interface PackConfig { + pack: string; + /** For provenance / flags. */ + sourceBook: string; + /** One or more source files that together make up this pack. */ + sources: SourceFile[]; + /** Skip chapters whose title matches this regex. */ + skip?: RegExp; +} + +// --------------------------------------------------------------------------- +// Pack configurations +// --------------------------------------------------------------------------- + +const PACK_CONFIGS: PackConfig[] = [ + { + pack: 'core-rules-lore', + sourceBook: 'core-rules', + sources: [{ path: 'ObsidianNotes/rules/00. Podręcznik Gry.md' }], + skip: /^(Talenty|Zaklęcia|Atrybuty|Umiejętności|Wyposażenie)/i, + }, + { + pack: 'bestiary-lore', + sourceBook: 'bestiary', + sources: [{ path: 'ObsidianNotes/rules/06. Bestiariusz.md' }], + skip: /^Talenty/i, + }, + + // ── Split books: combine lore/ chapters + Wstęp intro from rules/ ── + { + pack: 'magic-book-lore', + sourceBook: 'magic-book', + sources: [ + // Lore intro lives inside the rules file (no dedicated lore/ file). + { path: 'ObsidianNotes/rules/01. Księga Magii.md', prefaceTitle: 'Wstęp' }, + ], + skip: /^(Zaklęcia|Lista zaklęć|Rozdział V)/i, + }, + { + pack: 'abyss', + sourceBook: 'abyss-curse', + sources: [ + // Pure lore narrative. + { path: 'ObsidianNotes/lore/02. Otchłań i Magia.md' }, + // Rules intro (Wstęp only — mechanics chapters are skipped). + { 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, + }, + { + pack: 'blood-magic-history', + sourceBook: 'arcanum-sanguinis', + sources: [ + { path: 'ObsidianNotes/rules/04. Arcanum Sanguinis.md', prefaceTitle: 'Wstęp' }, + ], + skip: /^Rozdział IV - Talenty/i, + }, + { + pack: 'crimson-cult', + sourceBook: 'crimson-cult', + sources: [ + // Lore narrative (former Chwała Szkarłatnemu Kultowi lore section). + { path: 'ObsidianNotes/lore/05. Szkarłatny Kult.md' }, + // Rules intro only (spells/artifacts skipped). + { path: 'ObsidianNotes/rules/05. Vivat Patriarcha coccineus!.md', prefaceTitle: 'Wstęp' }, + ], + skip: /^(Rozdział I - Zaklęcia|Rozdział II - Artefakty)/i, + }, + { + pack: 'economy', + sourceBook: 'gold-steel-magic', + sources: [ + { path: 'ObsidianNotes/lore/03. Ekonomia Magicznego Świata.md' }, + { path: 'ObsidianNotes/rules/03. Złoto, Stal i Magia.md', prefaceTitle: 'Wstęp' }, + ], + skip: /^(Aneks A - Nowe Talenty|Aneks B - Przedmioty)/i, + }, + + // ── New: Humanity Guide ── + { + pack: 'humanity-guide', + sourceBook: 'humanity-guide', + sources: [{ path: 'ObsidianNotes/lore/10. Przewodnik Ludzkości po Magicznym Świecie.md' }], + }, +]; + +// --------------------------------------------------------------------------- +// Topical vault-folder configurations +// --------------------------------------------------------------------------- + +interface TopicalConfig { + /** Folder path relative to repoRoot. */ + folder: string; + /** Foundry pack id. */ + pack: string; +} + +const TOPICAL_CONFIGS: TopicalConfig[] = [ + { folder: 'ObsidianNotes/disciplines', pack: 'disciplines-lore' }, + { folder: 'ObsidianNotes/organizations', pack: 'organizations' }, + { folder: 'ObsidianNotes/races', pack: 'races-lore' }, + { folder: 'ObsidianNotes/classes', pack: 'classes' }, + { folder: 'ObsidianNotes/concepts', pack: 'concepts' }, + { folder: 'ObsidianNotes/locations', pack: 'locations' }, + { folder: 'ObsidianNotes/npcs', pack: 'npcs' }, + { folder: 'ObsidianNotes/conflicts', pack: 'conflicts' }, + { folder: 'ObsidianNotes/player-characters', pack: 'player-characters' }, + { folder: 'ObsidianNotes/adventures', pack: 'adventures' }, +]; + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +console.log(`[lore] repo root: ${repoRoot}`); + +const allDocs: JournalDoc[] = []; + +// ── Book packs ── +for (const cfg of PACK_CONFIGS) { + let packDocs: JournalDoc[] = []; + for (const src of cfg.sources) { + const bookPath = resolve(repoRoot, src.path); + if (!existsSync(bookPath)) { + console.warn(` · skip source ${src.path}: file not found`); + continue; + } + const docs = parseJournalBook({ + bookPath, + pack: cfg.pack, + sourceBook: cfg.sourceBook, + chapterSkip: cfg.skip, + prefaceTitle: src.prefaceTitle, + }); + + packDocs.push(...docs); + } + console.log(` · ${cfg.pack}: ${packDocs.length} entries`); + allDocs.push(...packDocs); +} + +// ── Topical packs ── +for (const top of TOPICAL_CONFIGS) { + const folderPath = resolve(repoRoot, top.folder); + if (!existsSync(folderPath)) { + console.warn(` · skip topical ${top.pack}: folder not found (${top.folder})`); + continue; + } + const docs = parseTopicalFolder({ folderPath, pack: top.pack }); + console.log(` · ${top.pack}: ${docs.length} entries`); + allDocs.push(...docs); +} + +console.log(`[lore] parsed ${allDocs.length} journal entries total`); + +if (existsSync(packsSrcDir)) rmSync(packsSrcDir, { recursive: true }); +const byPack = new Map(); +for (const doc of allDocs) { + const list = byPack.get(doc.pack) ?? []; + list.push(doc); + byPack.set(doc.pack, list); +} + +for (const [pack, docs] of byPack) { + const dir = resolve(packsSrcDir, pack); + mkdirSync(dir, { recursive: true }); + let sort = 0; + for (const doc of docs) { + sort += 100; + const foundryDoc = toFoundryJournal(doc, sort); + writeFileSync(resolve(dir, `${doc.id}.json`), `${JSON.stringify(foundryDoc, null, 2)}\n`, 'utf8'); + } + console.log(` · wrote ${pack}: ${docs.length} entries`); +} + +if (existsSync(packsOutDir)) rmSync(packsOutDir, { recursive: true }); +for (const pack of byPack.keys()) { + const src = resolve(packsSrcDir, pack); + const dest = resolve(packsOutDir, pack); + await compilePack(src, dest, { + recursive: false, + log: false, + transformEntry: (doc: any, context: any) => { + if (!doc._key) { + console.error(`[build-journal-packs] ERROR: Document missing _key! Name: ${doc.name}, ID: ${doc._id}`); + } + if (doc.pages) { + for (const page of doc.pages) { + if (!page._key) { + console.error(`[build-journal-packs] ERROR: Page missing _key! Journal: ${doc.name}, Page: ${page.name}, Page ID: ${page._id}`); + } + } + } + return true; + } + }); + console.log(` ✓ compiled ${pack}`); +} + +console.log(`[lore] done — ${byPack.size} packs in packs/`); + +function toFoundryJournal(doc: JournalDoc, sort: number): Record { + const fId = makeFoundryId(doc.id); + return { + _key: `!journal!${fId}`, + _id: fId, + name: doc.name, + folder: null, + sort, + pages: doc.pages.map((page, i) => { + const pageFId = makeFoundryId(`${doc.id}-page-${i}`); + return { + _key: `!journal.pages!${fId}.${pageFId}`, + _id: pageFId, + name: page.name, + type: 'text', + title: { show: true, level: 1 }, + text: { content: page.html, format: 1 /* HTML */ }, + sort: page.sort, + }; + }), + flags: { + 'hbm-rpg-v3-lore': { + slug: doc.id, + sourceBook: doc.source.book, + sourceChapter: doc.source.chapter, + sourceLine: doc.source.line, + }, + }, + _stats: {}, + }; +} + +/** + * Derive a stable 12-character Foundry document ID from a slug. + * Uses two independent FNV-1a 32-bit hashes to spread entropy across the + * *entire* slug (not just the first 12 bytes), avoiding collisions between + * slugs that share a long common prefix (e.g. page-0 vs page-1). + */ +function makeFoundryId(slug: string): string { + const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + // FNV-1a 32-bit, two independent passes with different offsets + let h1 = 0x811c9dc5; + let h2 = 0x4b9ace3f; + for (let i = 0; i < slug.length; i++) { + const c = slug.charCodeAt(i); + h1 = Math.imul(h1 ^ c, 0x01000193) >>> 0; + h2 = Math.imul(h2 ^ (c + i + 1), 0x01000193) >>> 0; + } + // Encode 64 bits (two 32-bit hashes) into 12 base-62 chars + let out = ''; + let lo = h1; + let hi = h2; + for (let i = 0; i < 12; i++) { + // Combine both halves cycling + const combined = (i % 2 === 0 ? lo : hi) >>> 0; + out += alphabet[combined % alphabet.length]; + lo = Math.imul(lo, 1664525) + 1013904223 >>> 0; + hi = Math.imul(hi, 22695477) + 1013904223 >>> 0; + } + return out; +} diff --git a/scripts/bump-version.ts b/scripts/bump-version.ts new file mode 100644 index 0000000..a0a9fd1 --- /dev/null +++ b/scripts/bump-version.ts @@ -0,0 +1,44 @@ +/** + * Bumps the patch version in package.json and module.json in lock-step. + * Run automatically as part of `bun run package`. + * + * Usage: + * bun scripts/bump-version.ts # patch bump (0.1.0 → 0.1.1) + * bun scripts/bump-version.ts minor # minor bump (0.1.0 → 0.2.0) + * bun scripts/bump-version.ts major # major bump (0.1.0 → 1.0.0) + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, '..'); + +type BumpType = 'major' | 'minor' | 'patch'; +const bump = (process.argv[2] ?? 'patch') as BumpType; + +function bumpVersion(version: string, type: BumpType): string { + const [major, minor, patch] = version.split('.').map(Number); + if (type === 'major') return `${major + 1}.0.0`; + if (type === 'minor') return `${major}.${minor + 1}.0`; + return `${major}.${minor}.${patch + 1}`; +} + +// --- package.json --- +const pkgPath = resolve(root, 'package.json'); +const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version: string }; +const oldVersion = pkg.version; +const newVersion = bumpVersion(oldVersion, bump); +pkg.version = newVersion; +writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + +// --- module.json --- +const modPath = resolve(root, 'module.json'); +const mod = JSON.parse(readFileSync(modPath, 'utf8')) as { version: string; download: string }; +mod.version = newVersion; +// Update the versioned filename in the download URL if it follows the vX.Y.Z pattern +mod.download = mod.download.replace(/-v[\d.]+\.zip$/, `-v${newVersion}.zip`); +writeFileSync(modPath, JSON.stringify(mod, null, 2) + '\n'); + +console.log(`✓ Version bumped ${oldVersion} → ${newVersion}`); diff --git a/scripts/lint-uuid-refs.ts b/scripts/lint-uuid-refs.ts new file mode 100644 index 0000000..8b207fe --- /dev/null +++ b/scripts/lint-uuid-refs.ts @@ -0,0 +1,59 @@ +/** + * Lint @UUID[…] references across packs-src to catch broken cross-links + * before packaging. Walks every JSON page in packs-src/, scans for + * `@UUID[Compendium.....]` patterns, and reports any reference + * to a system pack we know about but a slug that does not exist. + * + * Currently informational only — the journal parser does not yet emit + * UUID cross-links automatically. This script provides the framework + * for Phase 7.3/7.4 integration. + */ + +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const moduleRoot = resolve(__dirname, '..'); +const packsSrcDir = resolve(moduleRoot, 'packs-src'); + +const UUID_RE = /@UUID\[Compendium\.([^.\s]+)\.([^.\s]+)\.([^.\s]+)\.([^\]\s]+)\]/g; + +let scanned = 0; +let total = 0; +const refs: Array<{ file: string; pkg: string; pack: string; type: string; id: string }> = []; + +function walk(dir: string) { + for (const entry of readdirSync(dir)) { + const p = resolve(dir, entry); + const s = statSync(p); + if (s.isDirectory()) walk(p); + else if (entry.endsWith('.json')) scanFile(p); + } +} + +function scanFile(file: string) { + scanned++; + const text = readFileSync(file, 'utf8'); + let m: RegExpExecArray | null; + while ((m = UUID_RE.exec(text)) !== null) { + total++; + refs.push({ file, pkg: m[1], pack: m[2], type: m[3], id: m[4] }); + } +} + +try { + walk(packsSrcDir); +} catch { + console.log('[lint-uuid] no packs-src/ — run build:packs first.'); + process.exit(0); +} + +console.log(`[lint-uuid] scanned ${scanned} files; found ${total} @UUID refs.`); +if (refs.length > 0) { + for (const r of refs.slice(0, 25)) { + console.log(` · ${r.pkg}.${r.pack}.${r.type}.${r.id}`); + } + if (refs.length > 25) console.log(` … (+${refs.length - 25} more)`); +} +process.exit(0); diff --git a/scripts/package.ts b/scripts/package.ts new file mode 100644 index 0000000..1a62a32 --- /dev/null +++ b/scripts/package.ts @@ -0,0 +1,39 @@ +/** + * Package the lore module into a versioned zip ready to drop into a + * Foundry VTT instance's Data/modules/ directory. + */ + +import { createWriteStream, readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import archiver from 'archiver'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, '..'); +const moduleJson = JSON.parse(readFileSync(resolve(root, 'module.json'), 'utf8')) as { id: string; version: string }; +const outFile = resolve(root, `${moduleJson.id}-v${moduleJson.version}.zip`); + +await new Promise((resolveAll, rejectAll) => { + const output = createWriteStream(outFile); + const archive = archiver('zip', { zlib: { level: 9 } }); + + output.on('close', () => { + console.log(`✓ Packaged ${outFile} (${archive.pointer()} bytes)`); + resolveAll(); + }); + archive.on('warning', (err) => { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') console.warn(err); + else rejectAll(err); + }); + archive.on('error', rejectAll); + + archive.pipe(output); + // Foundry expects everything under a folder named after the module id. + archive.file(resolve(root, 'module.json'), { name: `${moduleJson.id}/module.json` }); + archive.directory(resolve(root, 'lang'), `${moduleJson.id}/lang`); + archive.directory(resolve(root, 'packs'), `${moduleJson.id}/packs`, (entry) => { + if (entry.name.endsWith('LOCK')) return false; + return entry; + }); + archive.finalize(); +}); diff --git a/scripts/parsers/journal-parser.ts b/scripts/parsers/journal-parser.ts new file mode 100644 index 0000000..2b563bf --- /dev/null +++ b/scripts/parsers/journal-parser.ts @@ -0,0 +1,300 @@ +/** + * Journal parser for HbM RPG v3 books. + * + * Strategy + * ──────── + * Supports two file formats: + * + * A. Legacy Google-Docs export (`_books/`): + * - No frontmatter. + * - TOC is a plain-text block that reuses the same `Rozdział X — Title` + * headings. The parser deduplicates: the SECOND occurrence of each + * heading is the real body. + * + * B. Native Obsidian/repo files (`rules/`, `lore/`): + * - Has YAML frontmatter (--- ... ---), stripped before parsing. + * - TOC is Obsidian bullet-links (`- [[#Anchor|Label]]`), not bare + * `Rozdział X` headings, so no duplicates exist and dedup is skipped. + * - Chapters may be prefixed with markdown heading markers (`## `). + * - An optional `prefaceTitle` captures content before the first chapter + * as a synthetic "Wstęp" entry. + * + * Output: `JournalDoc[]` consumed by `build-journal-packs.ts`. + */ + +import { readFileSync } from 'node:fs'; + +export interface JournalDoc { + /** Stable kebab-case slug. */ + id: string; + /** Display title (Polish, as in book). */ + name: string; + /** Pack id this entry belongs to (e.g. `abyss`). */ + pack: string; + /** Source provenance for traceability. */ + source: { book: string; chapter: string; line: number }; + /** Pages — each becomes a `JournalEntryPage`. */ + pages: JournalPage[]; +} + +export interface JournalPage { + name: string; + /** Content as HTML (paragraph-split markdown, basic conversion). */ + html: string; + /** Hint for sort order. */ + sort: number; +} + +export interface JournalParseConfig { + /** Absolute path to the markdown book. */ + bookPath: string; + /** Pack id to assign (e.g. `abyss`). */ + pack: string; + /** Source-book id for `source.book`. */ + sourceBook: string; + /** Optional: only emit chapters whose title matches this regex. */ + chapterFilter?: RegExp; + /** Optional: skip chapters whose title matches this regex (e.g. spell tables). */ + chapterSkip?: RegExp; + /** + * If set, content between the frontmatter/start and the first chapter is + * collected and emitted as a synthetic entry with this title (e.g. "Wstęp"). + * Useful for the intro paragraphs of split `rules/` files. + */ + prefaceTitle?: string; +} + +// Matches both plain-text (`Rozdział IV - Title`) and heading-prefixed +// (`## Rozdział IV - Title`) chapter lines. Optional trailing page number. +const CHAPTER_RE = /^(?:#{1,6}\s+)?Rozdzia[łl]\s+([IVXLC]+)\s*[:.—\-–]\s*(.+?)(?:\s+\d+)?$/; +const SEPARATOR = /^_{3,}$/; +const FRONTMATTER_FENCE = /^---\s*$/; +// Obsidian TOC bullets: `- [[#Anchor|Label]]` — skip in body content too +const OBSIDIAN_TOC_BULLET = /^[-*]\s+\[\[#/; + +/** Returns the first non-frontmatter line index (skips `--- ... ---` block). */ +function skipFrontmatter(lines: string[]): number { + if (lines.length === 0 || !FRONTMATTER_FENCE.test(lines[0].trim())) return 0; + for (let i = 1; i < lines.length; i++) { + if (FRONTMATTER_FENCE.test(lines[i].trim())) return i + 1; + } + return 0; +} + +export function parseJournalBook(cfg: JournalParseConfig): JournalDoc[] { + const text = readFileSync(cfg.bookPath, 'utf8'); + const rawLines = text.split(/\r?\n/); + const startIdx = skipFrontmatter(rawLines); + const lines = rawLines.slice(startIdx); + + // Pass 1: find every chapter occurrence. + const occurrences: Array<{ idx: number; roman: string; title: string; raw: string }> = []; + for (let i = 0; i < lines.length; i++) { + const m = lines[i].trim().match(CHAPTER_RE); + if (m) occurrences.push({ idx: i, roman: m[1], title: m[2].trim(), raw: lines[i].trim() }); + } + if (occurrences.length === 0) { + // No chapters found — if there's a prefaceTitle, still try to emit preface. + if (cfg.prefaceTitle) { + const pages = bodyToPages(lines); + if (pages.length > 0) { + const slug = slugify(cfg.prefaceTitle); + return [{ id: `${cfg.pack}-${slug}`, name: cfg.prefaceTitle, pack: cfg.pack, source: { book: cfg.sourceBook, chapter: cfg.prefaceTitle, line: startIdx + 1 }, pages }]; + } + } + return []; + } + + // Identify TOC entries: a chapter occurrence is part of the TOC if the + // SAME (roman, title) pair occurs again later in the file. + const seen = new Map(); + const bodyChapters: typeof occurrences = []; + for (const occ of occurrences) { + const key = `${occ.roman}|${occ.title}`; + if (!seen.has(key)) { + seen.set(key, occ.idx); + continue; + } + bodyChapters.push(occ); + } + // If no duplicates were found (no plain-text TOC — typical of native files), + // treat the entire list as body chapters. + const chapters = bodyChapters.length > 0 ? bodyChapters : occurrences; + + const docs: JournalDoc[] = []; + + // Emit preface entry (content before first chapter heading). + if (cfg.prefaceTitle && chapters.length > 0) { + const prefaceBody = lines.slice(0, chapters[0].idx); + // Filter out Obsidian TOC bullets so they don't become page content. + const prefaceClean = prefaceBody.filter((l) => !OBSIDIAN_TOC_BULLET.test(l.trim())); + const pages = bodyToPages(prefaceClean); + if (pages.length > 0) { + const slug = slugify(cfg.prefaceTitle); + docs.push({ + id: `${cfg.pack}-${slug}`, + name: cfg.prefaceTitle, + pack: cfg.pack, + source: { book: cfg.sourceBook, chapter: cfg.prefaceTitle, line: startIdx + 1 }, + pages, + }); + } + } + + for (let c = 0; c < chapters.length; c++) { + const ch = chapters[c]; + if (cfg.chapterSkip && cfg.chapterSkip.test(ch.title)) continue; + if (cfg.chapterFilter && !cfg.chapterFilter.test(ch.title)) continue; + + const start = ch.idx + 1; + const end = c + 1 < chapters.length ? chapters[c + 1].idx : lines.length; + const bodyRaw = lines.slice(start, end); + // Filter out Obsidian TOC bullets. + const body = bodyRaw.filter((l) => !OBSIDIAN_TOC_BULLET.test(l.trim())); + + const pages = bodyToPages(body); + if (pages.length === 0) continue; + + const slug = slugify(`${ch.roman}-${ch.title}`); + docs.push({ + id: `${cfg.pack}-${slug}`, + name: `Rozdział ${ch.roman} — ${ch.title}`, + pack: cfg.pack, + source: { book: cfg.sourceBook, chapter: ch.title, line: startIdx + ch.idx + 1 }, + pages, + }); + } + return docs; +} + +/** Split a chapter body by `____` separators into pages, converting each to HTML. */ +function bodyToPages(body: string[]): JournalPage[] { + const segments: string[][] = [[]]; + for (const line of body) { + if (SEPARATOR.test(line.trim())) { + if (segments[segments.length - 1].length > 0) segments.push([]); + } else { + segments[segments.length - 1].push(line); + } + } + const pages: JournalPage[] = []; + let pageNo = 1; + for (const seg of segments) { + const cleaned = trimEdges(seg); + if (cleaned.length === 0) continue; + const name = derivePageTitle(cleaned, pageNo); + pages.push({ name, html: linesToHtml(cleaned), sort: pageNo * 100 }); + pageNo++; + } + return pages; +} + +function trimEdges(arr: string[]): string[] { + let lo = 0; + let hi = arr.length; + while (lo < hi && arr[lo].trim() === '') lo++; + while (hi > lo && arr[hi - 1].trim() === '') hi--; + return arr.slice(lo, hi); +} + +/** First non-empty line if it looks short and title-like, else "Część N". */ +function derivePageTitle(lines: string[], pageNo: number): string { + const first = lines.find((l) => l.trim().length > 0)?.trim() ?? ''; + if (first.length > 0 && first.length <= 80 && !/[.!?]$/.test(first)) { + return first; + } + return `Część ${pageNo}`; +} + +/** Conservative markdown → HTML conversion. Paragraphs, bold, italic, lists. */ +function linesToHtml(lines: string[]): string { + const out: string[] = []; + let para: string[] = []; + let listType: 'ul' | 'ol' | null = null; + + const flushPara = () => { + if (para.length === 0) return; + out.push(`

${inline(para.join(' ').trim())}

`); + para = []; + }; + const flushList = () => { + if (listType) { + out.push(``); + listType = null; + } + }; + + for (const raw of lines) { + const line = raw.trimEnd(); + const trimmed = line.trim(); + + if (trimmed === '') { + flushPara(); + flushList(); + continue; + } + + const ulMatch = trimmed.match(/^[*\-]\s+(.+)$/); + const olMatch = trimmed.match(/^\d+\.\s+(.+)$/); + if (ulMatch) { + flushPara(); + if (listType !== 'ul') { + flushList(); + listType = 'ul'; + out.push('