vite-plugin-fvtt 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +124 -63
- package/package.json +1 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.5] - 2025-09-24
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `system.json` or `module.json` in root due to missing wait condition for the check not properly copying.
|
|
8
|
+
|
|
9
|
+
## [0.2.4] - 2025-09-23
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Removed dependencies of `fs-extra` and `dotenv` to shrink the dependencies.
|
|
14
|
+
- Async file loading should improve the performance for a large number of language files significantly.
|
|
15
|
+
|
|
3
16
|
## [0.2.3] - 2025-09-20
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import fs from "fs-extra";
|
|
2
1
|
import path from "path";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { glob } from "tinyglobby";
|
|
3
|
+
import fs from "fs/promises";
|
|
5
4
|
import { compilePack } from "@foundryvtt/foundryvtt-cli";
|
|
6
5
|
import { Server } from "socket.io";
|
|
7
6
|
import { io } from "socket.io-client";
|
|
@@ -9,17 +8,65 @@ import { io } from "socket.io-client";
|
|
|
9
8
|
//#region src/context.ts
|
|
10
9
|
const context = {};
|
|
11
10
|
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/utils/fs-utils.ts
|
|
13
|
+
var FsUtils = class FsUtils {
|
|
14
|
+
static async checkType(p, check) {
|
|
15
|
+
try {
|
|
16
|
+
const stats = await fs.stat(p);
|
|
17
|
+
return check(stats);
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static async fileExists(p) {
|
|
23
|
+
return FsUtils.checkType(p, (s) => s.isFile());
|
|
24
|
+
}
|
|
25
|
+
static async dirExists(p) {
|
|
26
|
+
return FsUtils.checkType(p, (s) => s.isDirectory());
|
|
27
|
+
}
|
|
28
|
+
static async readFile(filePath, encoding = "utf-8") {
|
|
29
|
+
return fs.readFile(filePath, { encoding });
|
|
30
|
+
}
|
|
31
|
+
static async readJson(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
const content = await FsUtils.readFile(filePath);
|
|
34
|
+
return JSON.parse(content);
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var fs_utils_default = FsUtils;
|
|
41
|
+
|
|
12
42
|
//#endregion
|
|
13
43
|
//#region src/config/env.ts
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
44
|
+
function parseEnv(content) {
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const line of content.split(/\r?\n/)) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
49
|
+
const [key, ...rest] = trimmed.split("=");
|
|
50
|
+
result[key.trim()] = rest.join("=").trim();
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
async function loadEnv() {
|
|
55
|
+
const envPaths = await glob(".env.foundryvtt*", { absolute: true });
|
|
56
|
+
let merged = {
|
|
57
|
+
FOUNDRY_URL: "localhost",
|
|
58
|
+
FOUNDRY_PORT: "30000"
|
|
59
|
+
};
|
|
60
|
+
for (const file of envPaths) {
|
|
61
|
+
const content = await fs_utils_default.readFile(file, "utf-8");
|
|
62
|
+
merged = {
|
|
63
|
+
...merged,
|
|
64
|
+
...parseEnv(content)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
20
67
|
return {
|
|
21
|
-
foundryUrl:
|
|
22
|
-
foundryPort: parseInt(
|
|
68
|
+
foundryUrl: merged.FOUNDRY_URL,
|
|
69
|
+
foundryPort: parseInt(merged.FOUNDRY_PORT, 10)
|
|
23
70
|
};
|
|
24
71
|
}
|
|
25
72
|
|
|
@@ -62,18 +109,20 @@ var logger_default = Logger;
|
|
|
62
109
|
|
|
63
110
|
//#endregion
|
|
64
111
|
//#region src/config/foundryvtt-manifest.ts
|
|
65
|
-
function loadManifest(config) {
|
|
112
|
+
async function loadManifest(config) {
|
|
66
113
|
if (context?.manifest) return context.manifest;
|
|
67
114
|
const publicDir = config.publicDir || "public";
|
|
68
|
-
const
|
|
115
|
+
const paths = [
|
|
69
116
|
"system.json",
|
|
70
117
|
"module.json",
|
|
71
118
|
`${publicDir}/system.json`,
|
|
72
119
|
`${publicDir}/module.json`
|
|
73
|
-
].map((
|
|
120
|
+
].map((f) => path.resolve(process.cwd(), f));
|
|
121
|
+
const idx = (await Promise.all(paths.map((p) => fs_utils_default.fileExists(p)))).findIndex(Boolean);
|
|
122
|
+
const foundPath = idx !== -1 ? paths[idx] : void 0;
|
|
74
123
|
if (!foundPath) logger_default.fail(`Could not find a manifest file (system.json or module.json) in project root or ${publicDir}/.`);
|
|
75
124
|
try {
|
|
76
|
-
const data =
|
|
125
|
+
const data = await fs_utils_default.readJson(foundPath);
|
|
77
126
|
if (!data.id || typeof data.id !== "string") logger_default.fail(`Manifest at ${foundPath} is missing required "id" field.`);
|
|
78
127
|
const hasEsmodules = Array.isArray(data.esmodules) && data.esmodules.length > 0;
|
|
79
128
|
const hasScripts = Array.isArray(data.scripts) && data.scripts.length > 0;
|
|
@@ -247,26 +296,28 @@ var PathUtils = class PathUtils {
|
|
|
247
296
|
}
|
|
248
297
|
return PathUtils._root;
|
|
249
298
|
}
|
|
250
|
-
static getOutDirFile(p) {
|
|
299
|
+
static async getOutDirFile(p) {
|
|
251
300
|
const file = path.join(PathUtils.getOutDir(), p);
|
|
252
|
-
return
|
|
301
|
+
return await fs_utils_default.fileExists(file) ? file : "";
|
|
253
302
|
}
|
|
254
|
-
static getPublicDirFile(p) {
|
|
303
|
+
static async getPublicDirFile(p) {
|
|
255
304
|
const file = path.join(PathUtils.getPublicDir(), p);
|
|
256
|
-
return
|
|
305
|
+
return await fs_utils_default.fileExists(file) ? file : "";
|
|
257
306
|
}
|
|
258
|
-
static findLocalFilePath(p) {
|
|
259
|
-
|
|
307
|
+
static async findLocalFilePath(p) {
|
|
308
|
+
const fileCandidates = [
|
|
260
309
|
PathUtils.getPublicDir(),
|
|
261
310
|
PathUtils.getSourceDirectory(),
|
|
262
311
|
PathUtils.getRoot()
|
|
263
|
-
].map((pth) => path.join(pth, p))
|
|
312
|
+
].map((pth) => path.join(pth, p));
|
|
313
|
+
const idx = (await Promise.all(fileCandidates.map(fs_utils_default.fileExists))).findIndex(Boolean);
|
|
314
|
+
return idx !== -1 ? fileCandidates[idx] : null;
|
|
264
315
|
}
|
|
265
316
|
static isFoundryVTTUrl(p) {
|
|
266
317
|
const decodedBase = PathUtils.getDecodedBase();
|
|
267
318
|
return path.posix.normalize(p).startsWith(decodedBase);
|
|
268
319
|
}
|
|
269
|
-
static foundryVTTUrlToLocal(p) {
|
|
320
|
+
static async foundryVTTUrlToLocal(p) {
|
|
270
321
|
const decodedBase = PathUtils.getDecodedBase();
|
|
271
322
|
let pathToTransform = path.posix.normalize("/" + p);
|
|
272
323
|
if (!pathToTransform.startsWith(decodedBase)) return null;
|
|
@@ -295,29 +346,33 @@ var path_utils_default = PathUtils;
|
|
|
295
346
|
|
|
296
347
|
//#endregion
|
|
297
348
|
//#region src/language/loader.ts
|
|
298
|
-
function getLocalLanguageFiles(lang, outDir = false) {
|
|
349
|
+
async function getLocalLanguageFiles(lang, outDir = false) {
|
|
299
350
|
const language = context.manifest.languages.find((l) => l.lang === lang);
|
|
300
351
|
if (!language) logger_default.fail(`Cannot find language "${lang}"`);
|
|
301
352
|
const langPath = language?.path ?? "";
|
|
302
|
-
if (outDir) return [path_utils_default.getOutDirFile(langPath)];
|
|
303
|
-
const publicDirFile = path_utils_default.getPublicDirFile(langPath);
|
|
353
|
+
if (outDir) return [await path_utils_default.getOutDirFile(langPath)];
|
|
354
|
+
const publicDirFile = await path_utils_default.getPublicDirFile(langPath);
|
|
304
355
|
if (publicDirFile !== "") return [publicDirFile];
|
|
305
356
|
const sourcePath = path_utils_default.getLanguageSourcePath(langPath, lang);
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
return globSync(path.join(sourcePath, "**/*.json"), { absolute: true });
|
|
357
|
+
if (await fs_utils_default.dirExists(sourcePath)) return await glob(path.join(sourcePath, "**/*.json"), { absolute: true });
|
|
358
|
+
logger_default.warn(`No language folder found at: ${sourcePath}`);
|
|
359
|
+
return [];
|
|
311
360
|
}
|
|
312
|
-
function loadLanguage(lang, outDir = false) {
|
|
313
|
-
const files = getLocalLanguageFiles(lang, outDir);
|
|
361
|
+
async function loadLanguage(lang, outDir = false) {
|
|
362
|
+
const files = await getLocalLanguageFiles(lang, outDir);
|
|
314
363
|
const result = /* @__PURE__ */ new Map();
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
364
|
+
const reads = files.map(async (file) => {
|
|
365
|
+
try {
|
|
366
|
+
const json = await fs_utils_default.readJson(file);
|
|
367
|
+
languageTracker.addFile(lang, file);
|
|
368
|
+
return [file, json];
|
|
369
|
+
} catch (e) {
|
|
370
|
+
logger_default.warn(e);
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
const results = await Promise.all(reads);
|
|
375
|
+
for (const entry of results) if (entry) result.set(entry[0], entry[1]);
|
|
321
376
|
return result;
|
|
322
377
|
}
|
|
323
378
|
|
|
@@ -356,9 +411,9 @@ function transform(dataMap) {
|
|
|
356
411
|
|
|
357
412
|
//#endregion
|
|
358
413
|
//#region src/language/validator.ts
|
|
359
|
-
function validator() {
|
|
414
|
+
async function validator() {
|
|
360
415
|
const manifest = context.manifest;
|
|
361
|
-
const baseLanguageData = loadLanguage("en", true);
|
|
416
|
+
const baseLanguageData = await loadLanguage("en", true);
|
|
362
417
|
if (baseLanguageData.size === 0) {
|
|
363
418
|
logger_default.error("Base language \"en\" not found or could not be loaded.");
|
|
364
419
|
return;
|
|
@@ -366,7 +421,7 @@ function validator() {
|
|
|
366
421
|
const base = flattenKeys(baseLanguageData.values().next().value);
|
|
367
422
|
for (const lang of manifest.languages) {
|
|
368
423
|
if (lang.lang === "en") continue;
|
|
369
|
-
const currentLanguageData = loadLanguage(lang.lang, true);
|
|
424
|
+
const currentLanguageData = await loadLanguage(lang.lang, true);
|
|
370
425
|
if (currentLanguageData.size === 0) {
|
|
371
426
|
logger_default.warn(`Summary for language [${lang.lang}]: Could not be loaded.`);
|
|
372
427
|
continue;
|
|
@@ -388,15 +443,19 @@ async function compileManifestPacks() {
|
|
|
388
443
|
for (const pack of context.manifest.packs) {
|
|
389
444
|
const srcCandidates = [path.resolve(path_utils_default.getSourceDirectory(), pack.path), path.resolve(path_utils_default.getRoot(), pack.path)];
|
|
390
445
|
const dest = path.resolve(path_utils_default.getOutDir(), pack.path);
|
|
391
|
-
|
|
446
|
+
let chosenSrc;
|
|
447
|
+
for (const candidate of srcCandidates) if (await fs_utils_default.dirExists(candidate)) {
|
|
448
|
+
chosenSrc = candidate;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
392
451
|
if (!chosenSrc) {
|
|
393
452
|
logger_default.warn(`Pack path not found for ${pack.path}, skipped.`);
|
|
394
453
|
continue;
|
|
395
454
|
}
|
|
396
|
-
const hasYaml =
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
})
|
|
455
|
+
const hasYaml = (await glob(["**/*.yaml", "**/*.yml"], {
|
|
456
|
+
cwd: chosenSrc,
|
|
457
|
+
absolute: true
|
|
458
|
+
})).length > 0;
|
|
400
459
|
await compilePack(chosenSrc, dest, {
|
|
401
460
|
yaml: hasYaml,
|
|
402
461
|
recursive: true
|
|
@@ -421,7 +480,7 @@ const handlebarsTracker = new HandlebarsTracker();
|
|
|
421
480
|
//#endregion
|
|
422
481
|
//#region src/server/http-middleware.ts
|
|
423
482
|
function httpMiddlewareHook(server) {
|
|
424
|
-
server.middlewares.use((req, res, next) => {
|
|
483
|
+
server.middlewares.use(async (req, res, next) => {
|
|
425
484
|
const config = context.config;
|
|
426
485
|
if (!path_utils_default.isFoundryVTTUrl(req.url ?? "")) {
|
|
427
486
|
next();
|
|
@@ -438,7 +497,7 @@ function httpMiddlewareHook(server) {
|
|
|
438
497
|
const languages = context.manifest.languages.filter((lang) => path_utils_default.localToFoundryVTTUrl(lang.path) === path.posix.normalize(req.url ?? ""));
|
|
439
498
|
if (languages.length === 1) {
|
|
440
499
|
const lang = languages[0].lang;
|
|
441
|
-
const language = loadLanguage(lang);
|
|
500
|
+
const language = await loadLanguage(lang);
|
|
442
501
|
const jsonData = transform(language);
|
|
443
502
|
res.setHeader("Content-Type", "application/json");
|
|
444
503
|
res.end(JSON.stringify(jsonData, null, 2));
|
|
@@ -458,13 +517,14 @@ function socketProxy(server) {
|
|
|
458
517
|
upgrade: false,
|
|
459
518
|
query: socket.handshake.query
|
|
460
519
|
});
|
|
461
|
-
socket.onAny((event, ...args) => {
|
|
520
|
+
socket.onAny(async (event, ...args) => {
|
|
462
521
|
const maybeAck = typeof args[args.length - 1] === "function" ? args.pop() : null;
|
|
463
522
|
if (event === "template") {
|
|
464
|
-
const localPath = path_utils_default.foundryVTTUrlToLocal(args[0]);
|
|
523
|
+
const localPath = await path_utils_default.foundryVTTUrlToLocal(args[0]);
|
|
465
524
|
if (localPath) {
|
|
525
|
+
const html = await fs_utils_default.readFile(localPath);
|
|
466
526
|
if (maybeAck) maybeAck({
|
|
467
|
-
html
|
|
527
|
+
html,
|
|
468
528
|
success: true
|
|
469
529
|
});
|
|
470
530
|
handlebarsTracker.addFile(args[0], localPath);
|
|
@@ -574,24 +634,23 @@ if (import.meta.hot) {
|
|
|
574
634
|
|
|
575
635
|
//#endregion
|
|
576
636
|
//#region src/index.ts
|
|
577
|
-
function foundryVTTPlugin(options = { buildPacks: true }) {
|
|
578
|
-
context.env = loadEnv();
|
|
637
|
+
async function foundryVTTPlugin(options = { buildPacks: true }) {
|
|
638
|
+
context.env = await loadEnv();
|
|
579
639
|
return {
|
|
580
640
|
name: "vite-plugin-fvtt",
|
|
581
|
-
config(config) {
|
|
582
|
-
context.manifest = loadManifest(config) ?? void 0;
|
|
641
|
+
async config(config) {
|
|
642
|
+
context.manifest = await loadManifest(config) ?? void 0;
|
|
583
643
|
return createPartialViteConfig(config);
|
|
584
644
|
},
|
|
585
645
|
configResolved(config) {
|
|
586
646
|
context.config = config;
|
|
587
647
|
},
|
|
588
648
|
async generateBundle() {
|
|
589
|
-
const
|
|
590
|
-
for (const file of manifestCandidates) {
|
|
649
|
+
for (const file of ["system.json", "module.json"]) {
|
|
591
650
|
const src = path.resolve(file);
|
|
592
|
-
if (!path_utils_default.getPublicDirFile(file) &&
|
|
651
|
+
if (!await path_utils_default.getPublicDirFile(file) && await fs_utils_default.fileExists(src)) {
|
|
593
652
|
this.addWatchFile(src);
|
|
594
|
-
const manifest =
|
|
653
|
+
const manifest = await fs_utils_default.readJson(src);
|
|
595
654
|
this.emitFile({
|
|
596
655
|
type: "asset",
|
|
597
656
|
fileName: file,
|
|
@@ -601,9 +660,11 @@ function foundryVTTPlugin(options = { buildPacks: true }) {
|
|
|
601
660
|
}
|
|
602
661
|
const languages = context.manifest?.languages ?? [];
|
|
603
662
|
if (languages.length > 0) for (const language of languages) {
|
|
604
|
-
if (path_utils_default.getPublicDirFile(language.path)) continue;
|
|
605
|
-
getLocalLanguageFiles(language.lang).
|
|
606
|
-
|
|
663
|
+
if (await path_utils_default.getPublicDirFile(language.path)) continue;
|
|
664
|
+
getLocalLanguageFiles(language.lang).then((langFiles) => {
|
|
665
|
+
langFiles.forEach((file) => this.addWatchFile(file));
|
|
666
|
+
});
|
|
667
|
+
const languageDataRaw = await loadLanguage(language.lang);
|
|
607
668
|
const languageData = transform(languageDataRaw);
|
|
608
669
|
this.emitFile({
|
|
609
670
|
type: "asset",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-fvtt",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "A Vite plugin for module and system development for Foundry VTT",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vite",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@eslint/js": "^9.34.0",
|
|
36
|
-
"@types/fs-extra": "^11.0.4",
|
|
37
36
|
"@types/node": "^24.3.0",
|
|
38
37
|
"eslint": "^9.34.0",
|
|
39
38
|
"prettier": "^3.6.2",
|
|
@@ -45,8 +44,6 @@
|
|
|
45
44
|
},
|
|
46
45
|
"dependencies": {
|
|
47
46
|
"@foundryvtt/foundryvtt-cli": "^3.0.0",
|
|
48
|
-
"dotenv": "^17.2.1",
|
|
49
|
-
"fs-extra": "^11.3.1",
|
|
50
47
|
"socket.io": "^4.8.1",
|
|
51
48
|
"socket.io-client": "^4.8.1",
|
|
52
49
|
"tinyglobby": "^0.2.15"
|