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 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
@@ -3,6 +3,6 @@ import { Plugin } from "vite";
3
3
  //#region src/index.d.ts
4
4
  declare function foundryVTTPlugin(options?: {
5
5
  buildPacks: boolean;
6
- }): Plugin;
6
+ }): Promise<Plugin>;
7
7
  //#endregion
8
8
  export { foundryVTTPlugin as default };
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
- import fs from "fs-extra";
2
1
  import path from "path";
3
- import dotenv from "dotenv";
4
- import { globSync } from "tinyglobby";
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 loadEnv() {
15
- const envPath = path.resolve(process.cwd(), ".env.foundryvtt.local");
16
- const { parsed } = dotenv.config({
17
- path: envPath,
18
- quiet: true
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: parsed?.FOUNDRY_URL ?? "localhost",
22
- foundryPort: parseInt(parsed?.FOUNDRY_PORT ?? "30000", 10)
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 foundPath = [
115
+ const paths = [
69
116
  "system.json",
70
117
  "module.json",
71
118
  `${publicDir}/system.json`,
72
119
  `${publicDir}/module.json`
73
- ].map((relPath) => path.resolve(process.cwd(), relPath)).find((absPath) => fs.pathExistsSync(absPath));
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 = fs.readJsonSync(foundPath);
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 fs.existsSync(file) ? file : "";
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 fs.existsSync(file) ? file : "";
305
+ return await fs_utils_default.fileExists(file) ? file : "";
257
306
  }
258
- static findLocalFilePath(p) {
259
- return [
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)).find((pth) => fs.existsSync(pth)) ?? null;
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 (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isDirectory()) {
307
- logger_default.warn(`No language folder found at: ${sourcePath}`);
308
- return [];
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
- for (const file of files) try {
316
- result.set(file, fs.readJSONSync(file));
317
- languageTracker.addFile(lang, file);
318
- } catch (e) {
319
- logger_default.warn(e);
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
- const chosenSrc = srcCandidates.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isDirectory());
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 = fs.readdirSync(chosenSrc, {
397
- recursive: true,
398
- encoding: "utf8"
399
- }).some((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml"));
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: fs.readFileSync(localPath, "utf8"),
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 manifestCandidates = ["system.json", "module.json"];
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) && fs.existsSync(src)) {
651
+ if (!await path_utils_default.getPublicDirFile(file) && await fs_utils_default.fileExists(src)) {
593
652
  this.addWatchFile(src);
594
- const manifest = fs.readJsonSync(src);
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).forEach((langFile) => this.addWatchFile(langFile));
606
- const languageDataRaw = loadLanguage(language.lang);
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",
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"