vite-plugin-fvtt 0.2.2 → 0.2.3

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,8 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## [0.2.2]
3
+ ## [0.2.3] - 2025-09-20
4
4
 
5
- ## Fixed
5
+ ### Fixed
6
+
7
+ - Return absolute paths from globbing.
8
+ - Refactor path-utils import and usage across codebase, for better Windows support.
9
+ - Refactor logger to use static Logger class methods.
10
+ - Bump dependencies.
11
+
12
+ ### Added
13
+
14
+ - Add Vitest and fixture-based build test.
15
+
16
+ ## [0.2.2] - 2025-09-10
17
+
18
+ ### Fixed
6
19
 
7
20
  - Improve windows path resolution.
8
21
 
@@ -75,7 +88,8 @@
75
88
 
76
89
  - Initial Release
77
90
 
78
- [unreleased]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.2...HEAD
91
+ [unreleased]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.3...HEAD
92
+ [0.2.3]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.2...v0.2.3
79
93
  [0.2.2]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.1...v0.2.2
80
94
  [0.2.1]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.0...v0.2.1
81
95
  [0.2.0]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.1.4...v0.2.0
package/README.md CHANGED
@@ -104,7 +104,8 @@ Merging follows your manifest’s declared language paths, searching in root or
104
104
 
105
105
  ### **6. Packs**
106
106
 
107
- Packs are tried to be auto-discovered in the source directory. If the paths match, they are automatically compiled.
107
+ Packs are tried to be auto-discovered in the source directory. If the paths match, they are
108
+ automatically compiled.
108
109
 
109
110
  **Note:** Packs are currently not watched for changes.
110
111
 
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import fs from "fs-extra";
2
- import posix from "path/posix";
3
- import dotenv from "dotenv";
4
2
  import path from "path";
3
+ import dotenv from "dotenv";
5
4
  import { globSync } from "tinyglobby";
6
5
  import { compilePack } from "@foundryvtt/foundryvtt-cli";
7
6
  import { Server } from "socket.io";
@@ -26,49 +25,52 @@ function loadEnv() {
26
25
 
27
26
  //#endregion
28
27
  //#region src/utils/logger.ts
29
- var Logger = class {
30
- namespace;
31
- colors = {
32
- info: "\x1B[32m",
28
+ var Logger = class Logger {
29
+ static namespace = "vite-plugin-fvtt";
30
+ static colors = {
31
+ info: "\x1B[36m",
33
32
  warn: "\x1B[33m",
34
33
  error: "\x1B[31m"
35
34
  };
36
- constructor({ namespace = "vite-plugin-fvtt" } = {}) {
37
- this.namespace = namespace;
35
+ static reset = "\x1B[0m";
36
+ initialize(namespace = "vite-plugin-fvtt") {
37
+ Logger.namespace = namespace;
38
+ }
39
+ static format(level, message) {
40
+ return `${Logger.colors[level] ?? ""}[${Logger.namespace}] [${level.toUpperCase()}]${Logger.reset} ${message}`;
38
41
  }
39
- format(level, message) {
40
- const color = this.colors[level] ?? "";
41
- const reset = "\x1B[0m";
42
- return `${color}[${this.namespace}] [${level.toUpperCase()}]${reset} ${message}`;
42
+ static info(message) {
43
+ console.log(Logger.format("info", message));
43
44
  }
44
- info(message) {
45
- console.log(this.format("info", message));
45
+ static warn(message) {
46
+ console.warn(Logger.format("warn", message));
46
47
  }
47
- warn(message) {
48
- console.warn(this.format("warn", message));
48
+ static error(message) {
49
+ console.error(Logger.format("error", message));
49
50
  }
50
- error(message) {
51
- console.error(this.format("error", message));
51
+ static fail(message) {
52
+ const formatted = Logger.format("error", Logger.stringify(message));
53
+ console.error(formatted);
54
+ throw new Error(formatted);
52
55
  }
53
- fail(message) {
54
- this.error(message);
55
- throw new Error(typeof message === "string" ? message : JSON.stringify(message, null, 2));
56
+ static stringify(message) {
57
+ if (message instanceof Error) return message.stack ?? message.message;
58
+ return typeof message === "string" ? message : JSON.stringify(message, null, 2);
56
59
  }
57
60
  };
58
- var logger_default = new Logger();
61
+ var logger_default = Logger;
59
62
 
60
63
  //#endregion
61
64
  //#region src/config/foundryvtt-manifest.ts
62
65
  function loadManifest(config) {
63
66
  if (context?.manifest) return context.manifest;
64
67
  const publicDir = config.publicDir || "public";
65
- const MANIFEST_LOCATIONS = [
68
+ const foundPath = [
66
69
  "system.json",
67
70
  "module.json",
68
71
  `${publicDir}/system.json`,
69
72
  `${publicDir}/module.json`
70
- ];
71
- const foundPath = MANIFEST_LOCATIONS.map((relPath) => path.resolve(process.cwd(), relPath)).find((absPath) => fs.pathExistsSync(absPath));
73
+ ].map((relPath) => path.resolve(process.cwd(), relPath)).find((absPath) => fs.pathExistsSync(absPath));
72
74
  if (!foundPath) logger_default.fail(`Could not find a manifest file (system.json or module.json) in project root or ${publicDir}/.`);
73
75
  try {
74
76
  const data = fs.readJsonSync(foundPath);
@@ -76,7 +78,7 @@ function loadManifest(config) {
76
78
  const hasEsmodules = Array.isArray(data.esmodules) && data.esmodules.length > 0;
77
79
  const hasScripts = Array.isArray(data.scripts) && data.scripts.length > 0;
78
80
  if (hasEsmodules === hasScripts) logger_default.fail(`Manifest at ${foundPath} must define exactly one of "esmodules" or "scripts".`);
79
- const result = {
81
+ return {
80
82
  manifestType: foundPath.includes("module.json") ? "module" : "system",
81
83
  id: data.id,
82
84
  esmodules: Array.isArray(data.esmodules) ? data.esmodules : [],
@@ -85,7 +87,6 @@ function loadManifest(config) {
85
87
  languages: Array.isArray(data.languages) ? data.languages : [],
86
88
  packs: Array.isArray(data.packs) ? data.packs : []
87
89
  };
88
- return result;
89
90
  } catch (err) {
90
91
  logger_default.fail(`Failed to read manifest at ${foundPath}: ${err?.message || err}`);
91
92
  }
@@ -122,8 +123,7 @@ function createPartialViteConfig(config) {
122
123
  rollupOptions: { output: {
123
124
  entryFileNames: fileName,
124
125
  assetFileNames: (assetInfo) => {
125
- const names = assetInfo.names ?? [];
126
- if (names.some((n) => n.endsWith(".css"))) return cssFileName;
126
+ if ((assetInfo.names ?? []).some((n) => n.endsWith(".css"))) return cssFileName;
127
127
  return "[name][extname]";
128
128
  }
129
129
  } }
@@ -220,8 +220,7 @@ var PathUtils = class PathUtils {
220
220
  static getSourceDirectory() {
221
221
  if (!PathUtils._sourceDirectory) {
222
222
  const config = PathUtils.getConfig();
223
- const normalizedEntry = path.normalize(config.build.lib.entry.toString());
224
- const segments = normalizedEntry.split(path.sep).filter(Boolean).filter((s) => s !== ".");
223
+ const segments = path.normalize(config.build.lib.entry.toString()).split(path.sep).filter(Boolean).filter((s) => s !== ".");
225
224
  const firstFolder = segments.length > 0 ? segments[0] : ".";
226
225
  PathUtils._sourceDirectory = path.join(config.root, firstFolder);
227
226
  }
@@ -257,17 +256,15 @@ var PathUtils = class PathUtils {
257
256
  return fs.existsSync(file) ? file : "";
258
257
  }
259
258
  static findLocalFilePath(p) {
260
- const fileCandidates = [
259
+ return [
261
260
  PathUtils.getPublicDir(),
262
261
  PathUtils.getSourceDirectory(),
263
262
  PathUtils.getRoot()
264
- ].map((pth) => path.join(pth, p));
265
- return fileCandidates.find((pth) => fs.existsSync(pth)) ?? null;
263
+ ].map((pth) => path.join(pth, p)).find((pth) => fs.existsSync(pth)) ?? null;
266
264
  }
267
265
  static isFoundryVTTUrl(p) {
268
266
  const decodedBase = PathUtils.getDecodedBase();
269
- const pathToCheck = path.posix.normalize(p);
270
- return pathToCheck.startsWith(decodedBase);
267
+ return path.posix.normalize(p).startsWith(decodedBase);
271
268
  }
272
269
  static foundryVTTUrlToLocal(p) {
273
270
  const decodedBase = PathUtils.getDecodedBase();
@@ -290,8 +287,7 @@ var PathUtils = class PathUtils {
290
287
  }
291
288
  static getLanguageSourcePath(p, lang) {
292
289
  const dir = path.parse(p).dir;
293
- const lastDirName = path.basename(dir);
294
- const finalSegments = lastDirName === lang ? dir : path.join(dir, lang);
290
+ const finalSegments = path.basename(dir) === lang ? dir : path.join(dir, lang);
295
291
  return path.join(PathUtils.getSourceDirectory(), finalSegments);
296
292
  }
297
293
  };
@@ -300,14 +296,10 @@ var path_utils_default = PathUtils;
300
296
  //#endregion
301
297
  //#region src/language/loader.ts
302
298
  function getLocalLanguageFiles(lang, outDir = false) {
303
- const manifest = context.manifest;
304
- const language = manifest.languages.find((l) => l.lang === lang);
299
+ const language = context.manifest.languages.find((l) => l.lang === lang);
305
300
  if (!language) logger_default.fail(`Cannot find language "${lang}"`);
306
301
  const langPath = language?.path ?? "";
307
- if (outDir) {
308
- const languageFile = path_utils_default.getOutDirFile(langPath);
309
- return [languageFile];
310
- }
302
+ if (outDir) return [path_utils_default.getOutDirFile(langPath)];
311
303
  const publicDirFile = path_utils_default.getPublicDirFile(langPath);
312
304
  if (publicDirFile !== "") return [publicDirFile];
313
305
  const sourcePath = path_utils_default.getLanguageSourcePath(langPath, lang);
@@ -315,7 +307,7 @@ function getLocalLanguageFiles(lang, outDir = false) {
315
307
  logger_default.warn(`No language folder found at: ${sourcePath}`);
316
308
  return [];
317
309
  }
318
- return globSync(posix.join(sourcePath, "**/*.json"));
310
+ return globSync(path.join(sourcePath, "**/*.json"), { absolute: true });
319
311
  }
320
312
  function loadLanguage(lang, outDir = false) {
321
313
  const files = getLocalLanguageFiles(lang, outDir);
@@ -376,15 +368,15 @@ function validator() {
376
368
  if (lang.lang === "en") continue;
377
369
  const currentLanguageData = loadLanguage(lang.lang, true);
378
370
  if (currentLanguageData.size === 0) {
379
- console.warn(`Summary for language [${lang.lang}]: Could not be loaded.`);
371
+ logger_default.warn(`Summary for language [${lang.lang}]: Could not be loaded.`);
380
372
  continue;
381
373
  }
382
374
  const current = flattenKeys(currentLanguageData.values().next().value);
383
375
  const missing = Object.keys(base).filter((key) => !(key in current));
384
376
  const extra = Object.keys(current).filter((key) => !(key in base));
385
- console.log(`Summary for language [${lang.lang}]:`);
386
- if (missing.length) console.warn(`\tMissing keys: ${missing.length}`, missing.slice(0, 5));
387
- if (extra.length) console.warn(`\tExtra keys: ${extra.length}`, extra.slice(0, 5));
377
+ logger_default.info(`Summary for language [${lang.lang}]:`);
378
+ if (missing.length) console.warn(`Missing keys: ${missing.length}`, missing.slice(0, 5));
379
+ if (extra.length) console.warn(`Extra keys: ${extra.length}`, extra.slice(0, 5));
388
380
  if (!missing.length && !extra.length) console.log(" ✅ All keys match.");
389
381
  }
390
382
  }
@@ -401,11 +393,10 @@ async function compileManifestPacks() {
401
393
  logger_default.warn(`Pack path not found for ${pack.path}, skipped.`);
402
394
  continue;
403
395
  }
404
- const entries = fs.readdirSync(chosenSrc, {
396
+ const hasYaml = fs.readdirSync(chosenSrc, {
405
397
  recursive: true,
406
398
  encoding: "utf8"
407
- });
408
- const hasYaml = entries.some((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml"));
399
+ }).some((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml"));
409
400
  await compilePack(chosenSrc, dest, {
410
401
  yaml: hasYaml,
411
402
  recursive: true
@@ -461,8 +452,7 @@ function httpMiddlewareHook(server) {
461
452
  //#region src/server/socket-proxy.ts
462
453
  function socketProxy(server) {
463
454
  const env = context.env;
464
- const ioProxy = new Server(server.httpServer, { path: "/socket.io" });
465
- ioProxy.on("connection", (socket) => {
455
+ new Server(server.httpServer, { path: "/socket.io" }).on("connection", (socket) => {
466
456
  const upstream = io(`http://${env.foundryUrl}:${env.foundryPort}`, {
467
457
  transports: ["websocket"],
468
458
  upgrade: false,
@@ -486,8 +476,7 @@ function socketProxy(server) {
486
476
  });
487
477
  });
488
478
  upstream.onAny((event, ...args) => {
489
- const lastArg = args[args.length - 1];
490
- const maybeAck = typeof lastArg === "function" ? args.pop() : null;
479
+ const maybeAck = typeof args[args.length - 1] === "function" ? args.pop() : null;
491
480
  socket.emit(event, ...args, (response) => {
492
481
  if (maybeAck) maybeAck(response);
493
482
  });
@@ -599,7 +588,7 @@ function foundryVTTPlugin(options = { buildPacks: true }) {
599
588
  async generateBundle() {
600
589
  const manifestCandidates = ["system.json", "module.json"];
601
590
  for (const file of manifestCandidates) {
602
- const src = posix.resolve(file);
591
+ const src = path.resolve(file);
603
592
  if (!path_utils_default.getPublicDirFile(file) && fs.existsSync(src)) {
604
593
  this.addWatchFile(src);
605
594
  const manifest = fs.readJsonSync(src);
@@ -618,7 +607,7 @@ function foundryVTTPlugin(options = { buildPacks: true }) {
618
607
  const languageData = transform(languageDataRaw);
619
608
  this.emitFile({
620
609
  type: "asset",
621
- fileName: posix.join(language.path),
610
+ fileName: path.join(language.path),
622
611
  source: JSON.stringify(languageData, null, 2)
623
612
  });
624
613
  }
@@ -627,17 +616,12 @@ function foundryVTTPlugin(options = { buildPacks: true }) {
627
616
  if (options.buildPacks) await compileManifestPacks();
628
617
  },
629
618
  closeBundle() {
630
- const languages = context.manifest?.languages ?? [];
631
- if (languages.length > 0) validator();
619
+ if ((context.manifest?.languages ?? []).length > 0) validator();
632
620
  },
633
621
  load(id) {
634
622
  const config = context.config;
635
623
  const jsFileName = (config.build.rollupOptions?.output).entryFileNames;
636
- if (id === jsFileName || id === `/${jsFileName}`) {
637
- const entryPath = posix.resolve(config.build.lib.entry);
638
- const viteId = `/@fs/${entryPath}`;
639
- return `import '${viteId}';\n${hmr_client_default}`;
640
- }
624
+ if (id === jsFileName || id === `/${jsFileName}`) return `import '${`/@fs/${path.resolve(config.build.lib.entry)}`}';\n${hmr_client_default}`;
641
625
  },
642
626
  configureServer: setupDevServer
643
627
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-fvtt",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A Vite plugin for module and system development for Foundry VTT",
5
5
  "keywords": [
6
6
  "vite",
@@ -28,7 +28,8 @@
28
28
  ],
29
29
  "scripts": {
30
30
  "dev": "tsdown --watch",
31
- "build": "tsdown"
31
+ "build": "tsdown",
32
+ "test": "vitest"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@eslint/js": "^9.34.0",
@@ -36,10 +37,11 @@
36
37
  "@types/node": "^24.3.0",
37
38
  "eslint": "^9.34.0",
38
39
  "prettier": "^3.6.2",
39
- "tsdown": "^0.14.2",
40
+ "tsdown": "^0.15.1",
40
41
  "typescript": "^5.9.2",
41
42
  "typescript-eslint": "^8.41.0",
42
- "vite": "*"
43
+ "vite": "*",
44
+ "vitest": "^3.2.4"
43
45
  },
44
46
  "dependencies": {
45
47
  "@foundryvtt/foundryvtt-cli": "^3.0.0",