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