vite-plugin-fvtt 0.2.4 → 0.2.6
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 +34 -2
- package/README.md +12 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +320 -289
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
[0.2.6] - 2025-10-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- CI now tests against Node.js Latest, 20 LTS, and 22 LTS, ensuring Foundry projects compile across
|
|
8
|
+
supported environments.
|
|
9
|
+
- Badges were added to the README for improved project visibility.
|
|
10
|
+
- Dependabot now tracks GitHub Actions, not just NPM dependencies.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- HMR logic updated to mirror Foundry V14's internal implementation, with a full fallback to V13
|
|
15
|
+
behavior for templates and JSON language files. _(If Foundry doesn't end up relying on the new
|
|
16
|
+
data shape in V14, this will have been an over-engineered no-op; but future-proofing beats
|
|
17
|
+
regret.)_
|
|
18
|
+
- ESLint configuration significantly tightened:
|
|
19
|
+
- Added sonarjs and unicorn plugins for deeper static analysis.
|
|
20
|
+
- Upgraded TypeScript ESLint rules from recommended to strict, increasing development signal
|
|
21
|
+
accuracy.
|
|
22
|
+
- Test suite refactored to reduce duplication and simplify onboarding for future test additions.
|
|
23
|
+
|
|
24
|
+
## [0.2.5] - 2025-09-24
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `system.json` or `module.json` in root due to missing wait condition for the check not properly
|
|
29
|
+
copying.
|
|
30
|
+
|
|
3
31
|
## [0.2.4] - 2025-09-23
|
|
4
32
|
|
|
5
33
|
### Changed
|
|
6
34
|
|
|
7
35
|
- Removed dependencies of `fs-extra` and `dotenv` to shrink the dependencies.
|
|
8
|
-
- Async file loading should improve the performance for a large number of language files
|
|
36
|
+
- Async file loading should improve the performance for a large number of language files
|
|
37
|
+
significantly.
|
|
9
38
|
|
|
10
39
|
## [0.2.3] - 2025-09-20
|
|
11
40
|
|
|
@@ -95,7 +124,10 @@
|
|
|
95
124
|
|
|
96
125
|
- Initial Release
|
|
97
126
|
|
|
98
|
-
[unreleased]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.
|
|
127
|
+
[unreleased]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.6...HEAD
|
|
128
|
+
[0.2.6]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.5...v0.2.6
|
|
129
|
+
[0.2.5]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.4...v0.2.5
|
|
130
|
+
[0.2.4]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.3...v0.2.4
|
|
99
131
|
[0.2.3]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.2...v0.2.3
|
|
100
132
|
[0.2.2]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.1...v0.2.2
|
|
101
133
|
[0.2.1]: https://github.com/MatyeusM/vite-plugin-fvtt/compare/v0.2.0...v0.2.1
|
package/README.md
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">vite-plugin-fvtt</h1>
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

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