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 +17 -3
- package/README.md +2 -1
- package/dist/index.js +49 -65
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [0.2.
|
|
3
|
+
## [0.2.3] - 2025-09-20
|
|
4
4
|
|
|
5
|
-
|
|
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.
|
|
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
|
|
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[
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
45
|
-
console.
|
|
45
|
+
static warn(message) {
|
|
46
|
+
console.warn(Logger.format("warn", message));
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
console.
|
|
48
|
+
static error(message) {
|
|
49
|
+
console.error(Logger.format("error", message));
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
386
|
-
if (missing.length) console.warn(
|
|
387
|
-
if (extra.length) console.warn(
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|