vite-plugin-fvtt 0.2.5 → 0.2.7
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 +36 -3
- package/README.md +12 -2
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +717 -0
- package/package.json +11 -5
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -692
package/dist/index.js
DELETED
|
@@ -1,692 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { glob } from "tinyglobby";
|
|
3
|
-
import fs from "fs/promises";
|
|
4
|
-
import { compilePack } from "@foundryvtt/foundryvtt-cli";
|
|
5
|
-
import { Server } from "socket.io";
|
|
6
|
-
import { io } from "socket.io-client";
|
|
7
|
-
|
|
8
|
-
//#region src/context.ts
|
|
9
|
-
const context = {};
|
|
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
|
-
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/config/env.ts
|
|
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
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
foundryUrl: merged.FOUNDRY_URL,
|
|
69
|
-
foundryPort: parseInt(merged.FOUNDRY_PORT, 10)
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
//#endregion
|
|
74
|
-
//#region src/utils/logger.ts
|
|
75
|
-
var Logger = class Logger {
|
|
76
|
-
static namespace = "vite-plugin-fvtt";
|
|
77
|
-
static colors = {
|
|
78
|
-
info: "\x1B[36m",
|
|
79
|
-
warn: "\x1B[33m",
|
|
80
|
-
error: "\x1B[31m"
|
|
81
|
-
};
|
|
82
|
-
static reset = "\x1B[0m";
|
|
83
|
-
initialize(namespace = "vite-plugin-fvtt") {
|
|
84
|
-
Logger.namespace = namespace;
|
|
85
|
-
}
|
|
86
|
-
static format(level, message) {
|
|
87
|
-
return `${Logger.colors[level] ?? ""}[${Logger.namespace}] [${level.toUpperCase()}]${Logger.reset} ${message}`;
|
|
88
|
-
}
|
|
89
|
-
static info(message) {
|
|
90
|
-
console.log(Logger.format("info", message));
|
|
91
|
-
}
|
|
92
|
-
static warn(message) {
|
|
93
|
-
console.warn(Logger.format("warn", message));
|
|
94
|
-
}
|
|
95
|
-
static error(message) {
|
|
96
|
-
console.error(Logger.format("error", message));
|
|
97
|
-
}
|
|
98
|
-
static fail(message) {
|
|
99
|
-
const formatted = Logger.format("error", Logger.stringify(message));
|
|
100
|
-
console.error(formatted);
|
|
101
|
-
throw new Error(formatted);
|
|
102
|
-
}
|
|
103
|
-
static stringify(message) {
|
|
104
|
-
if (message instanceof Error) return message.stack ?? message.message;
|
|
105
|
-
return typeof message === "string" ? message : JSON.stringify(message, null, 2);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
var logger_default = Logger;
|
|
109
|
-
|
|
110
|
-
//#endregion
|
|
111
|
-
//#region src/config/foundryvtt-manifest.ts
|
|
112
|
-
async function loadManifest(config) {
|
|
113
|
-
if (context?.manifest) return context.manifest;
|
|
114
|
-
const publicDir = config.publicDir || "public";
|
|
115
|
-
const paths = [
|
|
116
|
-
"system.json",
|
|
117
|
-
"module.json",
|
|
118
|
-
`${publicDir}/system.json`,
|
|
119
|
-
`${publicDir}/module.json`
|
|
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;
|
|
123
|
-
if (!foundPath) logger_default.fail(`Could not find a manifest file (system.json or module.json) in project root or ${publicDir}/.`);
|
|
124
|
-
try {
|
|
125
|
-
const data = await fs_utils_default.readJson(foundPath);
|
|
126
|
-
if (!data.id || typeof data.id !== "string") logger_default.fail(`Manifest at ${foundPath} is missing required "id" field.`);
|
|
127
|
-
const hasEsmodules = Array.isArray(data.esmodules) && data.esmodules.length > 0;
|
|
128
|
-
const hasScripts = Array.isArray(data.scripts) && data.scripts.length > 0;
|
|
129
|
-
if (hasEsmodules === hasScripts) logger_default.fail(`Manifest at ${foundPath} must define exactly one of "esmodules" or "scripts".`);
|
|
130
|
-
return {
|
|
131
|
-
manifestType: foundPath.includes("module.json") ? "module" : "system",
|
|
132
|
-
id: data.id,
|
|
133
|
-
esmodules: Array.isArray(data.esmodules) ? data.esmodules : [],
|
|
134
|
-
scripts: Array.isArray(data.scripts) ? data.scripts : [],
|
|
135
|
-
styles: Array.isArray(data.styles) ? data.styles : [],
|
|
136
|
-
languages: Array.isArray(data.languages) ? data.languages : [],
|
|
137
|
-
packs: Array.isArray(data.packs) ? data.packs : []
|
|
138
|
-
};
|
|
139
|
-
} catch (err) {
|
|
140
|
-
logger_default.fail(`Failed to read manifest at ${foundPath}: ${err?.message || err}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
//#endregion
|
|
145
|
-
//#region src/config/vite-options.ts
|
|
146
|
-
function createPartialViteConfig(config) {
|
|
147
|
-
const base = config.base ?? `/${context.manifest?.manifestType}s/${context.manifest?.id}/`;
|
|
148
|
-
const useEsModules = context.manifest?.esmodules.length === 1;
|
|
149
|
-
const formats = useEsModules ? ["es"] : ["umd"];
|
|
150
|
-
const fileName = (useEsModules ? context.manifest?.esmodules[0] : context.manifest?.scripts?.[0]) ?? "scripts/bundle.js";
|
|
151
|
-
if (!(useEsModules || context.manifest?.scripts?.[0])) logger_default.warn("No output file specified in manifest, using default \"bundle\" in the \"scripts/\" folder");
|
|
152
|
-
if (!context.manifest?.styles?.length) logger_default.warn("No CSS file found in manifest");
|
|
153
|
-
const cssFileName = context.manifest?.styles[0] ?? "styles/bundle.css";
|
|
154
|
-
if (!context.manifest?.styles[0]) logger_default.warn("No output css file specified in manifest, using default \"bundle\" in the \"styles/\" folder");
|
|
155
|
-
const foundryPort = context.env?.foundryPort ?? 3e4;
|
|
156
|
-
const foundryUrl = context.env?.foundryUrl ?? "localhost";
|
|
157
|
-
const entry = (config.build?.lib)?.entry;
|
|
158
|
-
if (!entry) logger_default.fail("Entry must be specified in lib");
|
|
159
|
-
if (typeof entry !== "string") logger_default.fail("Only a singular string entry is supported for build.lib.entry");
|
|
160
|
-
const isWatch = process.argv.includes("--watch") || !!config.build?.watch;
|
|
161
|
-
return {
|
|
162
|
-
base,
|
|
163
|
-
build: {
|
|
164
|
-
emptyOutDir: config.build?.emptyOutDir ?? !isWatch,
|
|
165
|
-
lib: {
|
|
166
|
-
entry,
|
|
167
|
-
formats,
|
|
168
|
-
name: context.manifest?.id ?? "bundle",
|
|
169
|
-
cssFileName: "bundle"
|
|
170
|
-
},
|
|
171
|
-
minify: "esbuild",
|
|
172
|
-
rollupOptions: { output: {
|
|
173
|
-
entryFileNames: fileName,
|
|
174
|
-
assetFileNames: (assetInfo) => {
|
|
175
|
-
if ((assetInfo.names ?? []).some((n) => n.endsWith(".css"))) return cssFileName;
|
|
176
|
-
return "[name][extname]";
|
|
177
|
-
}
|
|
178
|
-
} }
|
|
179
|
-
},
|
|
180
|
-
define: { __FVTT_PLUGIN__: {
|
|
181
|
-
id: context.manifest?.id,
|
|
182
|
-
isSystem: context.manifest?.manifestType === "system"
|
|
183
|
-
} },
|
|
184
|
-
esbuild: config.esbuild ?? {
|
|
185
|
-
minifyIdentifiers: false,
|
|
186
|
-
minifySyntax: true,
|
|
187
|
-
minifyWhitespace: true,
|
|
188
|
-
keepNames: true
|
|
189
|
-
},
|
|
190
|
-
server: {
|
|
191
|
-
port: foundryPort + 1,
|
|
192
|
-
proxy: { [`^(?!${base})`]: `http://${foundryUrl}:${foundryPort}` }
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
//#endregion
|
|
198
|
-
//#region src/server/trackers/abstract-file-tracker.ts
|
|
199
|
-
var AbstractFileTracker = class {
|
|
200
|
-
initialized = false;
|
|
201
|
-
tracked = /* @__PURE__ */ new Map();
|
|
202
|
-
watcher = null;
|
|
203
|
-
config;
|
|
204
|
-
constructor(config) {
|
|
205
|
-
this.config = config;
|
|
206
|
-
}
|
|
207
|
-
initialize(server) {
|
|
208
|
-
if (this.initialized) return;
|
|
209
|
-
this.initialized = true;
|
|
210
|
-
this.watcher = server.watcher;
|
|
211
|
-
this.watcher.on("change", (changedPath) => {
|
|
212
|
-
const value = this.tracked.get(changedPath);
|
|
213
|
-
if (!value) return;
|
|
214
|
-
logger_default.info(`Attempting to hot reload ${changedPath}`);
|
|
215
|
-
const eventData = this.getEventData(changedPath, value);
|
|
216
|
-
server.ws.send({
|
|
217
|
-
type: "custom",
|
|
218
|
-
event: this.updateEvent,
|
|
219
|
-
data: eventData
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
addFile(value, filePath) {
|
|
224
|
-
const absPath = path.resolve(filePath);
|
|
225
|
-
if (!this.tracked.has(absPath)) {
|
|
226
|
-
this.tracked.set(absPath, value);
|
|
227
|
-
this.watcher?.add(absPath);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region src/server/trackers/language-tracker.ts
|
|
234
|
-
var LanguageTracker = class extends AbstractFileTracker {
|
|
235
|
-
updateEvent = "foundryvtt-language-update";
|
|
236
|
-
constructor() {
|
|
237
|
-
super({});
|
|
238
|
-
}
|
|
239
|
-
getEventData() {
|
|
240
|
-
return {};
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
const languageTracker = new LanguageTracker();
|
|
244
|
-
|
|
245
|
-
//#endregion
|
|
246
|
-
//#region src/utils/path-utils.ts
|
|
247
|
-
var PathUtils = class PathUtils {
|
|
248
|
-
static _config = null;
|
|
249
|
-
static _sourceDirectory = null;
|
|
250
|
-
static _decodedBase = null;
|
|
251
|
-
static _publicDir = null;
|
|
252
|
-
static _outDir = null;
|
|
253
|
-
static _root = null;
|
|
254
|
-
static getConfig() {
|
|
255
|
-
if (!PathUtils._config) {
|
|
256
|
-
const config = context.config;
|
|
257
|
-
if (!config) logger_default.fail("Path utils can only be called after vite has resolved the config");
|
|
258
|
-
PathUtils._config = config;
|
|
259
|
-
}
|
|
260
|
-
return PathUtils._config;
|
|
261
|
-
}
|
|
262
|
-
static getDecodedBase() {
|
|
263
|
-
if (!PathUtils._decodedBase) {
|
|
264
|
-
const config = PathUtils.getConfig();
|
|
265
|
-
PathUtils._decodedBase = path.posix.normalize(decodeURI(config.base));
|
|
266
|
-
}
|
|
267
|
-
return PathUtils._decodedBase;
|
|
268
|
-
}
|
|
269
|
-
static getSourceDirectory() {
|
|
270
|
-
if (!PathUtils._sourceDirectory) {
|
|
271
|
-
const config = PathUtils.getConfig();
|
|
272
|
-
const segments = path.normalize(config.build.lib.entry.toString()).split(path.sep).filter(Boolean).filter((s) => s !== ".");
|
|
273
|
-
const firstFolder = segments.length > 0 ? segments[0] : ".";
|
|
274
|
-
PathUtils._sourceDirectory = path.join(config.root, firstFolder);
|
|
275
|
-
}
|
|
276
|
-
return PathUtils._sourceDirectory;
|
|
277
|
-
}
|
|
278
|
-
static getPublicDir() {
|
|
279
|
-
if (!PathUtils._publicDir) {
|
|
280
|
-
const config = PathUtils.getConfig();
|
|
281
|
-
PathUtils._publicDir = path.resolve(config.publicDir);
|
|
282
|
-
}
|
|
283
|
-
return PathUtils._publicDir;
|
|
284
|
-
}
|
|
285
|
-
static getOutDir() {
|
|
286
|
-
if (!PathUtils._outDir) {
|
|
287
|
-
const config = PathUtils.getConfig();
|
|
288
|
-
PathUtils._outDir = path.resolve(config.build.outDir);
|
|
289
|
-
}
|
|
290
|
-
return PathUtils._outDir;
|
|
291
|
-
}
|
|
292
|
-
static getRoot() {
|
|
293
|
-
if (!PathUtils._root) {
|
|
294
|
-
const config = PathUtils.getConfig();
|
|
295
|
-
PathUtils._root = path.resolve(config.root);
|
|
296
|
-
}
|
|
297
|
-
return PathUtils._root;
|
|
298
|
-
}
|
|
299
|
-
static async getOutDirFile(p) {
|
|
300
|
-
const file = path.join(PathUtils.getOutDir(), p);
|
|
301
|
-
return await fs_utils_default.fileExists(file) ? file : "";
|
|
302
|
-
}
|
|
303
|
-
static async getPublicDirFile(p) {
|
|
304
|
-
const file = path.join(PathUtils.getPublicDir(), p);
|
|
305
|
-
return await fs_utils_default.fileExists(file) ? file : "";
|
|
306
|
-
}
|
|
307
|
-
static async findLocalFilePath(p) {
|
|
308
|
-
const fileCandidates = [
|
|
309
|
-
PathUtils.getPublicDir(),
|
|
310
|
-
PathUtils.getSourceDirectory(),
|
|
311
|
-
PathUtils.getRoot()
|
|
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;
|
|
315
|
-
}
|
|
316
|
-
static isFoundryVTTUrl(p) {
|
|
317
|
-
const decodedBase = PathUtils.getDecodedBase();
|
|
318
|
-
return path.posix.normalize(p).startsWith(decodedBase);
|
|
319
|
-
}
|
|
320
|
-
static async foundryVTTUrlToLocal(p) {
|
|
321
|
-
const decodedBase = PathUtils.getDecodedBase();
|
|
322
|
-
let pathToTransform = path.posix.normalize("/" + p);
|
|
323
|
-
if (!pathToTransform.startsWith(decodedBase)) return null;
|
|
324
|
-
pathToTransform = path.relative(decodedBase, pathToTransform);
|
|
325
|
-
return PathUtils.findLocalFilePath(pathToTransform);
|
|
326
|
-
}
|
|
327
|
-
static localToFoundryVTTUrl(p) {
|
|
328
|
-
const decodedBase = PathUtils.getDecodedBase();
|
|
329
|
-
let pathToTransform = path.normalize(p);
|
|
330
|
-
[
|
|
331
|
-
PathUtils.getPublicDir(),
|
|
332
|
-
PathUtils.getSourceDirectory(),
|
|
333
|
-
PathUtils.getRoot()
|
|
334
|
-
].forEach((pth) => {
|
|
335
|
-
if (pathToTransform.startsWith(pth)) pathToTransform = pathToTransform.slice(pth.length);
|
|
336
|
-
});
|
|
337
|
-
return path.join(decodedBase, pathToTransform);
|
|
338
|
-
}
|
|
339
|
-
static getLanguageSourcePath(p, lang) {
|
|
340
|
-
const dir = path.parse(p).dir;
|
|
341
|
-
const finalSegments = path.basename(dir) === lang ? dir : path.join(dir, lang);
|
|
342
|
-
return path.join(PathUtils.getSourceDirectory(), finalSegments);
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
var path_utils_default = PathUtils;
|
|
346
|
-
|
|
347
|
-
//#endregion
|
|
348
|
-
//#region src/language/loader.ts
|
|
349
|
-
async function getLocalLanguageFiles(lang, outDir = false) {
|
|
350
|
-
const language = context.manifest.languages.find((l) => l.lang === lang);
|
|
351
|
-
if (!language) logger_default.fail(`Cannot find language "${lang}"`);
|
|
352
|
-
const langPath = language?.path ?? "";
|
|
353
|
-
if (outDir) return [await path_utils_default.getOutDirFile(langPath)];
|
|
354
|
-
const publicDirFile = await path_utils_default.getPublicDirFile(langPath);
|
|
355
|
-
if (publicDirFile !== "") return [publicDirFile];
|
|
356
|
-
const sourcePath = path_utils_default.getLanguageSourcePath(langPath, lang);
|
|
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 [];
|
|
360
|
-
}
|
|
361
|
-
async function loadLanguage(lang, outDir = false) {
|
|
362
|
-
const files = await getLocalLanguageFiles(lang, outDir);
|
|
363
|
-
const result = /* @__PURE__ */ new Map();
|
|
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]);
|
|
376
|
-
return result;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
//#endregion
|
|
380
|
-
//#region src/language/transformer.ts
|
|
381
|
-
function flattenKeys(obj, prefix = "") {
|
|
382
|
-
const result = {};
|
|
383
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
384
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
385
|
-
if (val && typeof val === "object" && !Array.isArray(val)) Object.assign(result, flattenKeys(val, fullKey));
|
|
386
|
-
else result[fullKey] = val;
|
|
387
|
-
}
|
|
388
|
-
return result;
|
|
389
|
-
}
|
|
390
|
-
function expandDotNotationKeys(target, source, depth = 0) {
|
|
391
|
-
if (depth > 32) logger_default.fail("Max object expansion depth exceeded.");
|
|
392
|
-
if (!source || typeof source !== "object" || Array.isArray(source)) return source;
|
|
393
|
-
for (const [key, value] of Object.entries(source)) {
|
|
394
|
-
let current = target;
|
|
395
|
-
const parts = key.split(".");
|
|
396
|
-
const lastKey = parts.pop();
|
|
397
|
-
for (const part of parts) {
|
|
398
|
-
if (!(part in current)) current[part] = {};
|
|
399
|
-
current = current[part];
|
|
400
|
-
}
|
|
401
|
-
if (lastKey in current) console.warn(`Warning: Overwriting key "${lastKey}" during transformation.`);
|
|
402
|
-
current[lastKey] = expandDotNotationKeys({}, value, depth + 1);
|
|
403
|
-
}
|
|
404
|
-
return target;
|
|
405
|
-
}
|
|
406
|
-
function transform(dataMap) {
|
|
407
|
-
const mergedData = {};
|
|
408
|
-
for (const data of dataMap.values()) expandDotNotationKeys(mergedData, data);
|
|
409
|
-
return mergedData;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
//#endregion
|
|
413
|
-
//#region src/language/validator.ts
|
|
414
|
-
async function validator() {
|
|
415
|
-
const manifest = context.manifest;
|
|
416
|
-
const baseLanguageData = await loadLanguage("en", true);
|
|
417
|
-
if (baseLanguageData.size === 0) {
|
|
418
|
-
logger_default.error("Base language \"en\" not found or could not be loaded.");
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
const base = flattenKeys(baseLanguageData.values().next().value);
|
|
422
|
-
for (const lang of manifest.languages) {
|
|
423
|
-
if (lang.lang === "en") continue;
|
|
424
|
-
const currentLanguageData = await loadLanguage(lang.lang, true);
|
|
425
|
-
if (currentLanguageData.size === 0) {
|
|
426
|
-
logger_default.warn(`Summary for language [${lang.lang}]: Could not be loaded.`);
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
const current = flattenKeys(currentLanguageData.values().next().value);
|
|
430
|
-
const missing = Object.keys(base).filter((key) => !(key in current));
|
|
431
|
-
const extra = Object.keys(current).filter((key) => !(key in base));
|
|
432
|
-
logger_default.info(`Summary for language [${lang.lang}]:`);
|
|
433
|
-
if (missing.length) console.warn(`Missing keys: ${missing.length}`, missing.slice(0, 5));
|
|
434
|
-
if (extra.length) console.warn(`Extra keys: ${extra.length}`, extra.slice(0, 5));
|
|
435
|
-
if (!missing.length && !extra.length) console.log(" ✅ All keys match.");
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
//#endregion
|
|
440
|
-
//#region src/packs/compile-packs.ts
|
|
441
|
-
async function compileManifestPacks() {
|
|
442
|
-
if (!context.manifest?.packs) return;
|
|
443
|
-
for (const pack of context.manifest.packs) {
|
|
444
|
-
const srcCandidates = [path.resolve(path_utils_default.getSourceDirectory(), pack.path), path.resolve(path_utils_default.getRoot(), pack.path)];
|
|
445
|
-
const dest = path.resolve(path_utils_default.getOutDir(), pack.path);
|
|
446
|
-
let chosenSrc;
|
|
447
|
-
for (const candidate of srcCandidates) if (await fs_utils_default.dirExists(candidate)) {
|
|
448
|
-
chosenSrc = candidate;
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
if (!chosenSrc) {
|
|
452
|
-
logger_default.warn(`Pack path not found for ${pack.path}, skipped.`);
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
const hasYaml = (await glob(["**/*.yaml", "**/*.yml"], {
|
|
456
|
-
cwd: chosenSrc,
|
|
457
|
-
absolute: true
|
|
458
|
-
})).length > 0;
|
|
459
|
-
await compilePack(chosenSrc, dest, {
|
|
460
|
-
yaml: hasYaml,
|
|
461
|
-
recursive: true
|
|
462
|
-
});
|
|
463
|
-
logger_default.info(`Compiled pack ${pack.path} (${hasYaml ? "YAML" : "JSON"}) from ${chosenSrc}`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
//#endregion
|
|
468
|
-
//#region src/server/trackers/handlebars-tracker.ts
|
|
469
|
-
var HandlebarsTracker = class extends AbstractFileTracker {
|
|
470
|
-
updateEvent = "foundryvtt-template-update";
|
|
471
|
-
constructor() {
|
|
472
|
-
super(context.config);
|
|
473
|
-
}
|
|
474
|
-
getEventData(changedPath, value) {
|
|
475
|
-
return { path: value };
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
const handlebarsTracker = new HandlebarsTracker();
|
|
479
|
-
|
|
480
|
-
//#endregion
|
|
481
|
-
//#region src/server/http-middleware.ts
|
|
482
|
-
function httpMiddlewareHook(server) {
|
|
483
|
-
server.middlewares.use(async (req, res, next) => {
|
|
484
|
-
const config = context.config;
|
|
485
|
-
if (!path_utils_default.isFoundryVTTUrl(req.url ?? "")) {
|
|
486
|
-
next();
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
const cssFileName = config.build.lib.cssFileName;
|
|
490
|
-
const cssEntry = cssFileName ? path_utils_default.localToFoundryVTTUrl(`${cssFileName}.css`) : null;
|
|
491
|
-
if (path.posix.normalize(req.url ?? "") === cssEntry) {
|
|
492
|
-
logger_default.info(`Blocking CSS entry to ${req.url}`);
|
|
493
|
-
res.setHeader("Content-Type", "text/css");
|
|
494
|
-
res.end("/* The cake is in another castle. */");
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
const languages = context.manifest.languages.filter((lang) => path_utils_default.localToFoundryVTTUrl(lang.path) === path.posix.normalize(req.url ?? ""));
|
|
498
|
-
if (languages.length === 1) {
|
|
499
|
-
const lang = languages[0].lang;
|
|
500
|
-
const language = await loadLanguage(lang);
|
|
501
|
-
const jsonData = transform(language);
|
|
502
|
-
res.setHeader("Content-Type", "application/json");
|
|
503
|
-
res.end(JSON.stringify(jsonData, null, 2));
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
next();
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
//#endregion
|
|
511
|
-
//#region src/server/socket-proxy.ts
|
|
512
|
-
function socketProxy(server) {
|
|
513
|
-
const env = context.env;
|
|
514
|
-
new Server(server.httpServer, { path: "/socket.io" }).on("connection", (socket) => {
|
|
515
|
-
const upstream = io(`http://${env.foundryUrl}:${env.foundryPort}`, {
|
|
516
|
-
transports: ["websocket"],
|
|
517
|
-
upgrade: false,
|
|
518
|
-
query: socket.handshake.query
|
|
519
|
-
});
|
|
520
|
-
socket.onAny(async (event, ...args) => {
|
|
521
|
-
const maybeAck = typeof args[args.length - 1] === "function" ? args.pop() : null;
|
|
522
|
-
if (event === "template") {
|
|
523
|
-
const localPath = await path_utils_default.foundryVTTUrlToLocal(args[0]);
|
|
524
|
-
if (localPath) {
|
|
525
|
-
const html = await fs_utils_default.readFile(localPath);
|
|
526
|
-
if (maybeAck) maybeAck({
|
|
527
|
-
html,
|
|
528
|
-
success: true
|
|
529
|
-
});
|
|
530
|
-
handlebarsTracker.addFile(args[0], localPath);
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
upstream.emit(event, ...args, (response) => {
|
|
535
|
-
if (maybeAck) maybeAck(response);
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
|
-
upstream.onAny((event, ...args) => {
|
|
539
|
-
const maybeAck = typeof args[args.length - 1] === "function" ? args.pop() : null;
|
|
540
|
-
socket.emit(event, ...args, (response) => {
|
|
541
|
-
if (maybeAck) maybeAck(response);
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
//#endregion
|
|
548
|
-
//#region src/server/index.ts
|
|
549
|
-
function setupDevServer(server) {
|
|
550
|
-
handlebarsTracker.initialize(server);
|
|
551
|
-
languageTracker.initialize(server);
|
|
552
|
-
httpMiddlewareHook(server);
|
|
553
|
-
socketProxy(server);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
//#endregion
|
|
557
|
-
//#region src/server/hmr-client.ts
|
|
558
|
-
var hmr_client_default = `
|
|
559
|
-
if (import.meta.hot) {
|
|
560
|
-
const FVTT_PLUGIN = __FVTT_PLUGIN__
|
|
561
|
-
|
|
562
|
-
function refreshApplications(path = null) {
|
|
563
|
-
// AppV1 refresh
|
|
564
|
-
Object.values(foundry.ui.windows).forEach(app => app.render(true))
|
|
565
|
-
// AppV2 refresh
|
|
566
|
-
if (path)
|
|
567
|
-
foundry.applications.instances.forEach(appV2 => {
|
|
568
|
-
Object.values(appV2.constructor.PARTS ?? {}).forEach(part => {
|
|
569
|
-
const templates = Array.isArray(part.templates) ? part.templates : []
|
|
570
|
-
if (part.template === path || templates.includes(path)) appV2.render(true)
|
|
571
|
-
})
|
|
572
|
-
})
|
|
573
|
-
else foundry.applications.instances.forEach(appV2 => appV2.render(true))
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
import.meta.hot.on('foundryvtt-template-update', ({ path }) => {
|
|
577
|
-
game.socket.emit('template', path, response => {
|
|
578
|
-
if (response.error) new Error(response.error)
|
|
579
|
-
let template = undefined
|
|
580
|
-
try {
|
|
581
|
-
template = Handlebars.compile(response.html)
|
|
582
|
-
} catch (error) {
|
|
583
|
-
console.error(error)
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
|
-
Handlebars.registerPartial(path, template)
|
|
587
|
-
console.log(\`Vite | Retrieved and compiled template \${path}\`)
|
|
588
|
-
refreshApplications(path)
|
|
589
|
-
})
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
async function hmrLanguage(lang, targetObject = game.i18n.translations) {
|
|
593
|
-
try {
|
|
594
|
-
const languages = FVTT_PLUGIN.isSystem
|
|
595
|
-
? game.system.languages
|
|
596
|
-
: game.modules.get(FVTT_PLUGIN.id)?.languages
|
|
597
|
-
if (!languages) {
|
|
598
|
-
console.warn(
|
|
599
|
-
'Vite | Got a HMR request to reload languages, however no languages were found.',
|
|
600
|
-
)
|
|
601
|
-
return
|
|
602
|
-
}
|
|
603
|
-
const langEntry = languages.find(l => l.lang === lang)
|
|
604
|
-
if (!langEntry) {
|
|
605
|
-
console.warn('Vite | Got an HMR request for an undefined language')
|
|
606
|
-
return
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const url = langEntry.path
|
|
610
|
-
const resp = await fetch(url)
|
|
611
|
-
if (!resp.ok) throw new Error('Failed to fetch language file!')
|
|
612
|
-
|
|
613
|
-
const json = await resp.json()
|
|
614
|
-
|
|
615
|
-
foundry.utils.mergeObject(targetObject, json)
|
|
616
|
-
console.log(\`Vite | HMR: Reloaded language '\${lang}'\`)
|
|
617
|
-
} catch (error) {
|
|
618
|
-
console.error(\`Vite | HMR: Error reloading language '\${lang}' for \${FVTT_PLUGIN.id}\`, error);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
import.meta.hot.on('foundryvtt-language-update', async () => {
|
|
623
|
-
const currentLang = game.i18n.lang
|
|
624
|
-
const promises = []
|
|
625
|
-
if (currentLang !== 'en') {
|
|
626
|
-
promises.push(hmrLanguage('en', game.i18n._fallback))
|
|
627
|
-
}
|
|
628
|
-
promises.push(hmrLanguage(currentLang))
|
|
629
|
-
await Promise.all(promises)
|
|
630
|
-
refreshApplications()
|
|
631
|
-
})
|
|
632
|
-
} else console.error('Vite | HMR is disabled')
|
|
633
|
-
//`;
|
|
634
|
-
|
|
635
|
-
//#endregion
|
|
636
|
-
//#region src/index.ts
|
|
637
|
-
async function foundryVTTPlugin(options = { buildPacks: true }) {
|
|
638
|
-
context.env = await loadEnv();
|
|
639
|
-
return {
|
|
640
|
-
name: "vite-plugin-fvtt",
|
|
641
|
-
async config(config) {
|
|
642
|
-
context.manifest = await loadManifest(config) ?? void 0;
|
|
643
|
-
return createPartialViteConfig(config);
|
|
644
|
-
},
|
|
645
|
-
configResolved(config) {
|
|
646
|
-
context.config = config;
|
|
647
|
-
},
|
|
648
|
-
async generateBundle() {
|
|
649
|
-
for (const file of ["system.json", "module.json"]) {
|
|
650
|
-
const src = path.resolve(file);
|
|
651
|
-
if (!await path_utils_default.getPublicDirFile(file) && await fs_utils_default.fileExists(src)) {
|
|
652
|
-
this.addWatchFile(src);
|
|
653
|
-
const manifest = await fs_utils_default.readJson(src);
|
|
654
|
-
this.emitFile({
|
|
655
|
-
type: "asset",
|
|
656
|
-
fileName: file,
|
|
657
|
-
source: JSON.stringify(manifest, null, 2)
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
const languages = context.manifest?.languages ?? [];
|
|
662
|
-
if (languages.length > 0) for (const language of languages) {
|
|
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);
|
|
668
|
-
const languageData = transform(languageDataRaw);
|
|
669
|
-
this.emitFile({
|
|
670
|
-
type: "asset",
|
|
671
|
-
fileName: path.join(language.path),
|
|
672
|
-
source: JSON.stringify(languageData, null, 2)
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
},
|
|
676
|
-
async writeBundle() {
|
|
677
|
-
if (options.buildPacks) await compileManifestPacks();
|
|
678
|
-
},
|
|
679
|
-
closeBundle() {
|
|
680
|
-
if ((context.manifest?.languages ?? []).length > 0) validator();
|
|
681
|
-
},
|
|
682
|
-
load(id) {
|
|
683
|
-
const config = context.config;
|
|
684
|
-
const jsFileName = (config.build.rollupOptions?.output).entryFileNames;
|
|
685
|
-
if (id === jsFileName || id === `/${jsFileName}`) return `import '${`/@fs/${path.resolve(config.build.lib.entry)}`}';\n${hmr_client_default}`;
|
|
686
|
-
},
|
|
687
|
-
configureServer: setupDevServer
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
//#endregion
|
|
692
|
-
export { foundryVTTPlugin as default };
|