@inlang/paraglide-js 2.27.0 → 2.29.0
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/README.md +9 -0
- package/dist/bundler-plugins/per-locale-build/index.js +14 -7
- package/dist/bundler-plugins/per-locale-build/index.js.map +1 -1
- package/dist/bundler-plugins/per-locale-build/vite-environments.test.js +235 -2
- package/dist/bundler-plugins/per-locale-build/vite-environments.test.js.map +1 -1
- package/dist/bundler-plugins/unplugin.d.ts +4 -1
- package/dist/bundler-plugins/unplugin.js +134 -22
- package/dist/bundler-plugins/unplugin.js.map +1 -1
- package/dist/bundler-plugins/unplugin.test.js +490 -1
- package/dist/bundler-plugins/unplugin.test.js.map +1 -1
- package/dist/bundler-plugins/vite-error-response.test.d.ts +1 -0
- package/dist/bundler-plugins/vite-error-response.test.js +171 -0
- package/dist/bundler-plugins/vite-error-response.test.js.map +1 -0
- package/dist/bundler-plugins/vite.d.ts +8 -0
- package/dist/bundler-plugins/vite.js +14 -4
- package/dist/bundler-plugins/vite.js.map +1 -1
- package/dist/bundler-plugins/vite.test.js +1 -1
- package/dist/bundler-plugins/vite.test.js.map +1 -1
- package/dist/cli/commands/compile/command.js +13 -0
- package/dist/cli/commands/compile/command.js.map +1 -1
- package/dist/cli/commands/compile/command.test.js +87 -3
- package/dist/cli/commands/compile/command.test.js.map +1 -1
- package/dist/cli/commands/init/command.d.ts +5 -0
- package/dist/cli/commands/init/command.js +213 -59
- package/dist/cli/commands/init/command.js.map +1 -1
- package/dist/cli/commands/init/command.test.d.ts +1 -0
- package/dist/cli/commands/init/command.test.js +123 -0
- package/dist/cli/commands/init/command.test.js.map +1 -0
- package/dist/cli/init-options.d.ts +11 -0
- package/dist/cli/init-options.js +2 -0
- package/dist/cli/init-options.js.map +1 -0
- package/dist/cli/steps/add-inlang-check.js +2 -2
- package/dist/cli/steps/add-inlang-check.js.map +1 -1
- package/dist/cli/steps/add-message-lint.d.ts +34 -0
- package/dist/cli/steps/add-message-lint.js +105 -0
- package/dist/cli/steps/add-message-lint.js.map +1 -0
- package/dist/cli/steps/add-message-lint.test.d.ts +1 -0
- package/dist/cli/steps/add-message-lint.test.js +74 -0
- package/dist/cli/steps/add-message-lint.test.js.map +1 -0
- package/dist/cli/steps/add-vite-plugin.d.ts +18 -0
- package/dist/cli/steps/add-vite-plugin.js +34 -6
- package/dist/cli/steps/add-vite-plugin.js.map +1 -1
- package/dist/cli/steps/add-vite-plugin.test.js +25 -9
- package/dist/cli/steps/add-vite-plugin.test.js.map +1 -1
- package/dist/cli/steps/check-for-uncomitted-changes.d.ts +3 -0
- package/dist/cli/steps/check-for-uncomitted-changes.js +15 -3
- package/dist/cli/steps/check-for-uncomitted-changes.js.map +1 -1
- package/dist/cli/steps/detect-bundler.d.ts +1 -0
- package/dist/cli/steps/detect-bundler.js +7 -1
- package/dist/cli/steps/detect-bundler.js.map +1 -1
- package/dist/cli/steps/initialize-inlang-project.d.ts +4 -3
- package/dist/cli/steps/initialize-inlang-project.js +60 -157
- package/dist/cli/steps/initialize-inlang-project.js.map +1 -1
- package/dist/cli/steps/maybe-add-machine-translation.d.ts +4 -0
- package/dist/cli/steps/maybe-add-machine-translation.js +11 -6
- package/dist/cli/steps/maybe-add-machine-translation.js.map +1 -1
- package/dist/cli/steps/maybe-add-sherlock.d.ts +2 -0
- package/dist/cli/steps/maybe-add-sherlock.js +12 -6
- package/dist/cli/steps/maybe-add-sherlock.js.map +1 -1
- package/dist/cli/steps/maybe-add-sherlock.test.d.ts +1 -0
- package/dist/cli/steps/maybe-add-sherlock.test.js +44 -0
- package/dist/cli/steps/maybe-add-sherlock.test.js.map +1 -0
- package/dist/cli/steps/prompt-for-outdir.d.ts +2 -0
- package/dist/cli/steps/prompt-for-outdir.js +4 -0
- package/dist/cli/steps/prompt-for-outdir.js.map +1 -1
- package/dist/cli/steps/update-package-json.js +8 -13
- package/dist/cli/steps/update-package-json.js.map +1 -1
- package/dist/cli/steps/update-ts-config.d.ts +6 -0
- package/dist/cli/steps/update-ts-config.js +27 -4
- package/dist/cli/steps/update-ts-config.js.map +1 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.js +6 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/utils.test.d.ts +1 -0
- package/dist/cli/utils.test.js +16 -0
- package/dist/cli/utils.test.js.map +1 -0
- package/dist/compiler/compile-annotation.js +6 -1
- package/dist/compiler/compile-annotation.js.map +1 -1
- package/dist/compiler/compile-bundle.d.ts +4 -0
- package/dist/compiler/compile-bundle.js +67 -2
- package/dist/compiler/compile-bundle.js.map +1 -1
- package/dist/compiler/compile-bundle.test.js +214 -1
- package/dist/compiler/compile-bundle.test.js.map +1 -1
- package/dist/compiler/compile-lifecycle.test.d.ts +1 -0
- package/dist/compiler/compile-lifecycle.test.js +69 -0
- package/dist/compiler/compile-lifecycle.test.js.map +1 -0
- package/dist/compiler/compile-local-variable.test.js +14 -14
- package/dist/compiler/compile-local-variable.test.js.map +1 -1
- package/dist/compiler/compile-message.exact-number.test.js +1 -1
- package/dist/compiler/compile-message.exact-number.test.js.map +1 -1
- package/dist/compiler/compile-message.locals.test.js +1 -1
- package/dist/compiler/compile-message.locals.test.js.map +1 -1
- package/dist/compiler/compile-message.test.js +2 -2
- package/dist/compiler/compile-message.test.js.map +1 -1
- package/dist/compiler/compile-pattern.test.js +5 -5
- package/dist/compiler/compile-pattern.test.js.map +1 -1
- package/dist/compiler/compile-project.js +1 -0
- package/dist/compiler/compile-project.js.map +1 -1
- package/dist/compiler/compile-project.test.js +94 -1
- package/dist/compiler/compile-project.test.js.map +1 -1
- package/dist/compiler/compile.js +36 -17
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/compile.test.js +70 -0
- package/dist/compiler/compile.test.js.map +1 -1
- package/dist/compiler/compiler-options.d.ts +41 -0
- package/dist/compiler/compiler-options.js.map +1 -1
- package/dist/compiler/output-structure/message-modules.js +1 -1
- package/dist/compiler/output-structure/message-modules.js.map +1 -1
- package/dist/compiler/registry.js +71 -13
- package/dist/compiler/registry.js.map +1 -1
- package/dist/compiler/registry.test.d.ts +1 -0
- package/dist/compiler/registry.test.js +291 -0
- package/dist/compiler/registry.test.js.map +1 -0
- package/dist/compiler/runtime/create-runtime.d.ts +2 -0
- package/dist/compiler/runtime/create-runtime.js +9 -0
- package/dist/compiler/runtime/create-runtime.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.js +8 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.test.js +26 -0
- package/dist/compiler/runtime/extract-locale-from-cookie.test.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-header.js +47 -5
- package/dist/compiler/runtime/extract-locale-from-header.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-header.test.js +58 -0
- package/dist/compiler/runtime/extract-locale-from-header.test.js.map +1 -1
- package/dist/compiler/runtime/get-locale.js +7 -1
- package/dist/compiler/runtime/get-locale.js.map +1 -1
- package/dist/compiler/runtime/get-locale.test.js +95 -0
- package/dist/compiler/runtime/get-locale.test.js.map +1 -1
- package/dist/compiler/runtime/localize-href.d.ts +8 -0
- package/dist/compiler/runtime/localize-href.js +60 -0
- package/dist/compiler/runtime/localize-href.js.map +1 -1
- package/dist/compiler/runtime/localize-href.test.js +227 -1
- package/dist/compiler/runtime/localize-href.test.js.map +1 -1
- package/dist/compiler/runtime/localize-url.d.ts +8 -4
- package/dist/compiler/runtime/localize-url.js +12 -8
- package/dist/compiler/runtime/localize-url.js.map +1 -1
- package/dist/compiler/runtime/localize-url.test.js +45 -0
- package/dist/compiler/runtime/localize-url.test.js.map +1 -1
- package/dist/compiler/runtime/set-locale.js +24 -6
- package/dist/compiler/runtime/set-locale.js.map +1 -1
- package/dist/compiler/runtime/set-locale.test.js +182 -0
- package/dist/compiler/runtime/set-locale.test.js.map +1 -1
- package/dist/compiler/runtime/type.d.ts +2 -0
- package/dist/compiler/runtime/type.js.map +1 -1
- package/dist/compiler/runtime/type.test.js +75 -0
- package/dist/compiler/runtime/type.test.js.map +1 -1
- package/dist/compiler/runtime/variables.d.ts +2 -0
- package/dist/compiler/runtime/variables.js +7 -0
- package/dist/compiler/runtime/variables.js.map +1 -1
- package/dist/compiler/server/middleware.d.ts +8 -2
- package/dist/compiler/server/middleware.js +175 -7
- package/dist/compiler/server/middleware.js.map +1 -1
- package/dist/compiler/server/middleware.test.js +750 -3
- package/dist/compiler/server/middleware.test.js.map +1 -1
- package/dist/eslint/index.d.ts +4 -0
- package/dist/eslint/index.js +8 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/no-dynamic-message-access.d.ts +3 -0
- package/dist/eslint/no-dynamic-message-access.js +150 -0
- package/dist/eslint/no-dynamic-message-access.js.map +1 -0
- package/dist/eslint/no-dynamic-message-access.test.d.ts +1 -0
- package/dist/eslint/no-dynamic-message-access.test.js +126 -0
- package/dist/eslint/no-dynamic-message-access.test.js.map +1 -0
- package/dist/eslint/oxlint.test.d.ts +1 -0
- package/dist/eslint/oxlint.test.js +64 -0
- package/dist/eslint/oxlint.test.js.map +1 -0
- package/dist/services/config/config-schema.d.ts +2 -0
- package/dist/services/config/config-schema.js +2 -0
- package/dist/services/config/config-schema.js.map +1 -1
- package/dist/services/config/load-paraglide-config.test.js +12 -0
- package/dist/services/config/load-paraglide-config.test.js.map +1 -1
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/env-variables/index.js.map +1 -1
- package/dist/services/file-handling/write-output.bench.js +51 -11
- package/dist/services/file-handling/write-output.bench.js.map +1 -1
- package/dist/services/file-handling/write-output.js +60 -20
- package/dist/services/file-handling/write-output.js.map +1 -1
- package/dist/services/file-handling/write-output.test.js +175 -0
- package/dist/services/file-handling/write-output.test.js.map +1 -1
- package/package.json +14 -4
|
@@ -35,7 +35,7 @@ afterEach(async () => {
|
|
|
35
35
|
async function createProjectFixture(options = {}) {
|
|
36
36
|
const { mkdtemp, mkdir, writeFile } = await import("node:fs/promises");
|
|
37
37
|
const { tmpdir } = await import("node:os");
|
|
38
|
-
const workspace = await mkdtemp(path.join(tmpdir(), "paraglide-plugin-"));
|
|
38
|
+
const workspace = realFs.realpathSync(await mkdtemp(path.join(tmpdir(), "paraglide-plugin-")));
|
|
39
39
|
testDirectories.push(workspace);
|
|
40
40
|
const projectDir = path.join(workspace, "project.inlang");
|
|
41
41
|
await mkdir(projectDir, { recursive: true });
|
|
@@ -67,6 +67,493 @@ function setViteRoot(plugin, root) {
|
|
|
67
67
|
configResolved?.handler?.({ root });
|
|
68
68
|
}
|
|
69
69
|
const mockContext = { addWatchFile: () => { } };
|
|
70
|
+
test("closing Vite waits for an in-flight watch compilation", async () => {
|
|
71
|
+
process.env.NODE_ENV = "development";
|
|
72
|
+
const { workspace, projectDir, outdir } = await createProjectFixture();
|
|
73
|
+
let notifyEntered;
|
|
74
|
+
let releaseCompilation;
|
|
75
|
+
const entered = new Promise((resolve) => {
|
|
76
|
+
notifyEntered = resolve;
|
|
77
|
+
});
|
|
78
|
+
const released = new Promise((resolve) => {
|
|
79
|
+
releaseCompilation = resolve;
|
|
80
|
+
});
|
|
81
|
+
let shouldBlock = false;
|
|
82
|
+
vi.doMock("../compiler/compile.js", async (importOriginal) => {
|
|
83
|
+
const original = await importOriginal();
|
|
84
|
+
return {
|
|
85
|
+
...original,
|
|
86
|
+
compile: async (...args) => {
|
|
87
|
+
if (shouldBlock) {
|
|
88
|
+
notifyEntered();
|
|
89
|
+
await released;
|
|
90
|
+
}
|
|
91
|
+
return original.compile(...args);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
const { createServer } = await import("vite");
|
|
96
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
97
|
+
const server = await createServer({
|
|
98
|
+
root: workspace,
|
|
99
|
+
configFile: false,
|
|
100
|
+
logLevel: "silent",
|
|
101
|
+
plugins: [paraglideVitePlugin({ project: projectDir, outdir })],
|
|
102
|
+
server: { middlewareMode: true, watch: null },
|
|
103
|
+
optimizeDeps: { noDiscovery: true, include: [] },
|
|
104
|
+
});
|
|
105
|
+
let pending;
|
|
106
|
+
try {
|
|
107
|
+
const file = path.join(projectDir, "settings.json");
|
|
108
|
+
const settings = JSON.parse(await realFs.promises.readFile(file, "utf8"));
|
|
109
|
+
await realFs.promises.writeFile(file, JSON.stringify({ ...settings, baseLocale: "de" }));
|
|
110
|
+
shouldBlock = true;
|
|
111
|
+
pending = server.pluginContainer.watchChange(file, { event: "update" });
|
|
112
|
+
await entered;
|
|
113
|
+
let didClose = false;
|
|
114
|
+
const closing = server.close().then(() => {
|
|
115
|
+
didClose = true;
|
|
116
|
+
});
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
118
|
+
expect(didClose).toBe(false);
|
|
119
|
+
releaseCompilation();
|
|
120
|
+
await closing;
|
|
121
|
+
expect(await realFs.promises.readFile(path.join(outdir, "runtime.js"), "utf8")).toContain('baseLocale = "de"');
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
releaseCompilation();
|
|
125
|
+
await pending;
|
|
126
|
+
await server.close();
|
|
127
|
+
vi.doUnmock("../compiler/compile.js");
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
function configureViteServer(plugin) {
|
|
131
|
+
const ws = {
|
|
132
|
+
send: vi.fn(),
|
|
133
|
+
clients: new Set([{}]),
|
|
134
|
+
on: vi.fn(),
|
|
135
|
+
};
|
|
136
|
+
plugin.configureServer?.({ ws });
|
|
137
|
+
return ws;
|
|
138
|
+
}
|
|
139
|
+
test.each(["settings", "config"])("ignores late %s events after close and resumes on buildStart", async (source) => {
|
|
140
|
+
process.env.NODE_ENV = "development";
|
|
141
|
+
const { workspace, projectDir, outdir } = await createProjectFixture();
|
|
142
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
143
|
+
const plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
144
|
+
setViteRoot(plugin, workspace);
|
|
145
|
+
await plugin.buildStart.call(mockContext);
|
|
146
|
+
await plugin.closeBundle.call(mockContext);
|
|
147
|
+
const runtime = path.join(outdir, "runtime.js");
|
|
148
|
+
const before = await realFs.promises.readFile(runtime, "utf8");
|
|
149
|
+
const file = path.join(projectDir, source === "settings" ? "settings.json" : "paraglide.config.js");
|
|
150
|
+
if (source === "settings") {
|
|
151
|
+
const settings = JSON.parse(await realFs.promises.readFile(file, "utf8"));
|
|
152
|
+
await realFs.promises.writeFile(file, JSON.stringify({ ...settings, baseLocale: "de" }));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
await realFs.promises.writeFile(file, 'export default { cookieName: "after-close" };');
|
|
156
|
+
}
|
|
157
|
+
await plugin.watchChange.call(mockContext, file);
|
|
158
|
+
expect(await realFs.promises.readFile(runtime, "utf8")).toBe(before);
|
|
159
|
+
await plugin.buildStart.call(mockContext);
|
|
160
|
+
expect(await realFs.promises.readFile(runtime, "utf8")).toContain(source === "settings" ? 'baseLocale = "de"' : 'cookieName = "after-close"');
|
|
161
|
+
});
|
|
162
|
+
test.each(["same instance", "new instance", "cold restart"].flatMap((restart) => [true, false].map((notifyLateChange) => ({ restart, notifyLateChange }))))("rebuilds after a shutdown input race: $restart, late event=$notifyLateChange", async ({ restart, notifyLateChange }) => {
|
|
163
|
+
process.env.NODE_ENV = "development";
|
|
164
|
+
const { workspace, projectDir, outdir } = await createProjectFixture();
|
|
165
|
+
let notifyCompiled;
|
|
166
|
+
let releaseCompilation;
|
|
167
|
+
const compiled = new Promise((resolve) => {
|
|
168
|
+
notifyCompiled = resolve;
|
|
169
|
+
});
|
|
170
|
+
const released = new Promise((resolve) => {
|
|
171
|
+
releaseCompilation = resolve;
|
|
172
|
+
});
|
|
173
|
+
let shouldBlock = false;
|
|
174
|
+
let compilationCount = 0;
|
|
175
|
+
vi.doMock("../compiler/compile.js", async (importOriginal) => {
|
|
176
|
+
const original = await importOriginal();
|
|
177
|
+
return {
|
|
178
|
+
...original,
|
|
179
|
+
compile: async (...args) => {
|
|
180
|
+
compilationCount++;
|
|
181
|
+
const result = await original.compile(...args);
|
|
182
|
+
if (shouldBlock) {
|
|
183
|
+
// Pause after the input has been consumed, before the plugin
|
|
184
|
+
// re-reads it to compute the cached digest.
|
|
185
|
+
notifyCompiled();
|
|
186
|
+
await released;
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
let { paraglideVitePlugin } = await import("./vite.js");
|
|
193
|
+
let plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
194
|
+
setViteRoot(plugin, workspace);
|
|
195
|
+
let pending;
|
|
196
|
+
try {
|
|
197
|
+
await plugin.buildStart.call(mockContext);
|
|
198
|
+
const file = path.join(projectDir, "settings.json");
|
|
199
|
+
const original = await realFs.promises.readFile(file, "utf8");
|
|
200
|
+
await realFs.promises.writeFile(file, JSON.stringify({ ...JSON.parse(original), baseLocale: "de" }));
|
|
201
|
+
shouldBlock = true;
|
|
202
|
+
pending = plugin.watchChange.call(mockContext, file);
|
|
203
|
+
await compiled;
|
|
204
|
+
const closing = plugin.closeBundle.call(mockContext);
|
|
205
|
+
await realFs.promises.writeFile(file, original);
|
|
206
|
+
const lateChange = notifyLateChange
|
|
207
|
+
? plugin.watchChange.call(mockContext, file)
|
|
208
|
+
: undefined;
|
|
209
|
+
releaseCompilation();
|
|
210
|
+
await Promise.all([pending, closing, lateChange]);
|
|
211
|
+
shouldBlock = false;
|
|
212
|
+
// Shutdown drains the existing work without forcing another compile.
|
|
213
|
+
expect(compilationCount).toBe(2);
|
|
214
|
+
const runtime = path.join(outdir, "runtime.js");
|
|
215
|
+
expect(await realFs.promises.readFile(runtime, "utf8")).toContain('baseLocale = "de"');
|
|
216
|
+
if (restart === "cold restart") {
|
|
217
|
+
// Drop the warm state so only the on-disk cache can be reused.
|
|
218
|
+
vi.resetModules();
|
|
219
|
+
({ paraglideVitePlugin } = await import("./vite.js"));
|
|
220
|
+
}
|
|
221
|
+
if (restart !== "same instance") {
|
|
222
|
+
plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
223
|
+
setViteRoot(plugin, workspace);
|
|
224
|
+
}
|
|
225
|
+
await plugin.buildStart.call(mockContext);
|
|
226
|
+
expect(compilationCount).toBe(3);
|
|
227
|
+
expect(await realFs.promises.readFile(runtime, "utf8")).toContain('baseLocale = "en"');
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
releaseCompilation();
|
|
231
|
+
await pending;
|
|
232
|
+
vi.doUnmock("../compiler/compile.js");
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
async function connectViteClient(server) {
|
|
236
|
+
const url = new URL(server.resolvedUrls.local[0]);
|
|
237
|
+
url.protocol = "ws:";
|
|
238
|
+
url.searchParams.set("token", server.config.webSocketToken);
|
|
239
|
+
const socket = new WebSocket(url, "vite-hmr");
|
|
240
|
+
const messages = [];
|
|
241
|
+
socket.addEventListener("message", (event) => {
|
|
242
|
+
messages.push(JSON.parse(String(event.data)));
|
|
243
|
+
});
|
|
244
|
+
await vi.waitFor(() => {
|
|
245
|
+
expect(messages).toContainEqual({ type: "connected" });
|
|
246
|
+
});
|
|
247
|
+
// A message sent after connection callbacks provides a transport barrier.
|
|
248
|
+
server.ws.send({ type: "custom", event: "paraglide:test-barrier" });
|
|
249
|
+
await vi.waitFor(() => {
|
|
250
|
+
expect(messages).toContainEqual({
|
|
251
|
+
type: "custom",
|
|
252
|
+
event: "paraglide:test-barrier",
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
return { socket, messages };
|
|
256
|
+
}
|
|
257
|
+
async function startOverlayServer(invalidSettings = false) {
|
|
258
|
+
process.env.NODE_ENV = "development";
|
|
259
|
+
const { createServer } = await import("vite");
|
|
260
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
261
|
+
const fixture = await createProjectFixture();
|
|
262
|
+
const { workspace, projectDir, outdir } = fixture;
|
|
263
|
+
const file = path.join(projectDir, "settings.json");
|
|
264
|
+
const original = await realFs.promises.readFile(file, "utf8");
|
|
265
|
+
if (invalidSettings)
|
|
266
|
+
await realFs.promises.writeFile(file, "{");
|
|
267
|
+
await realFs.promises.writeFile(path.join(workspace, "index.html"), "<html><body>Translation preview</body></html>");
|
|
268
|
+
const server = await createServer({
|
|
269
|
+
root: workspace,
|
|
270
|
+
configFile: false,
|
|
271
|
+
logLevel: "silent",
|
|
272
|
+
plugins: [paraglideVitePlugin({ project: projectDir, outdir })],
|
|
273
|
+
server: { host: "127.0.0.1", port: 0, watch: null },
|
|
274
|
+
optimizeDeps: { noDiscovery: true, include: [] },
|
|
275
|
+
});
|
|
276
|
+
try {
|
|
277
|
+
await server.listen();
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
await server.close();
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
return { ...fixture, server, file, original };
|
|
284
|
+
}
|
|
285
|
+
test.each(["settings", "config"])("vite error overlay reports invalid %s at startup and clears after recovery", async (source) => {
|
|
286
|
+
process.env.NODE_ENV = "development";
|
|
287
|
+
const { projectDir, outdir } = await createProjectFixture();
|
|
288
|
+
const file = path.join(projectDir, source === "settings" ? "settings.json" : "paraglide.config.js");
|
|
289
|
+
const original = await realFs.promises.readFile(file, "utf8");
|
|
290
|
+
await realFs.promises.writeFile(file, source === "settings" ? "{" : "export default { outdir: 42 }");
|
|
291
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
292
|
+
const plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
293
|
+
const { send } = configureViteServer(plugin);
|
|
294
|
+
await expect(plugin.buildStart.call(mockContext)).resolves.toBeUndefined();
|
|
295
|
+
expect(send).toHaveBeenCalledWith({
|
|
296
|
+
type: "error",
|
|
297
|
+
err: {
|
|
298
|
+
message: expect.any(String),
|
|
299
|
+
stack: expect.any(String),
|
|
300
|
+
plugin: "unplugin-paraglide-js",
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
expect(send.mock.calls[0][0].err.message.length).toBeGreaterThan(0);
|
|
304
|
+
await realFs.promises.writeFile(file, original);
|
|
305
|
+
await plugin.watchChange.call(mockContext, file);
|
|
306
|
+
expect(send).toHaveBeenLastCalledWith({ type: "full-reload" });
|
|
307
|
+
expect(await realFs.promises.readdir(outdir)).toContain("runtime.js");
|
|
308
|
+
});
|
|
309
|
+
test.each(["settings", "config"])("vite error overlay recovers from invalid %s and preserves the last output until recovery", async (source) => {
|
|
310
|
+
process.env.NODE_ENV = "development";
|
|
311
|
+
const { projectDir, outdir } = await createProjectFixture();
|
|
312
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
313
|
+
const writeFile = vi.spyOn(realFs.promises, "writeFile");
|
|
314
|
+
const plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
315
|
+
const { send } = configureViteServer(plugin);
|
|
316
|
+
await plugin.buildStart.call(mockContext);
|
|
317
|
+
expect(send).not.toHaveBeenCalled();
|
|
318
|
+
const runtime = await realFs.promises.readFile(path.join(outdir, "runtime.js"), "utf8");
|
|
319
|
+
const file = path.join(projectDir, source === "settings" ? "settings.json" : "paraglide.config.js");
|
|
320
|
+
const original = await realFs.promises.readFile(file, "utf8");
|
|
321
|
+
await realFs.promises.writeFile(file, source === "settings" ? "{" : "export default { outdir: 42 }");
|
|
322
|
+
await expect(plugin.watchChange.call(mockContext, file)).resolves.toBeUndefined();
|
|
323
|
+
expect(send).toHaveBeenLastCalledWith({
|
|
324
|
+
type: "error",
|
|
325
|
+
err: expect.objectContaining({ message: expect.any(String) }),
|
|
326
|
+
});
|
|
327
|
+
expect(await realFs.promises.readFile(path.join(outdir, "runtime.js"), "utf8")).toBe(runtime);
|
|
328
|
+
await plugin.watchChange.call(mockContext, path.join(outdir, "runtime.js"));
|
|
329
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
330
|
+
await realFs.promises.writeFile(file, original);
|
|
331
|
+
writeFile.mockClear();
|
|
332
|
+
await plugin.watchChange.call(mockContext, file);
|
|
333
|
+
expect(send).toHaveBeenLastCalledWith({ type: "full-reload" });
|
|
334
|
+
const outputWrites = writeFile.mock.calls.filter(([file]) => String(file).startsWith(outdir));
|
|
335
|
+
if (source === "config") {
|
|
336
|
+
expect(outputWrites).toHaveLength(0);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
expect(outputWrites.length).toBeGreaterThan(0);
|
|
340
|
+
}
|
|
341
|
+
await plugin.watchChange.call(mockContext, file);
|
|
342
|
+
expect(send).toHaveBeenCalledTimes(2);
|
|
343
|
+
});
|
|
344
|
+
test("vite error overlay prevents generated HMR while the config remains invalid", async () => {
|
|
345
|
+
const { server, workspace, projectDir, outdir, file, original } = await startOverlayServer();
|
|
346
|
+
try {
|
|
347
|
+
const main = path.join(workspace, "main.js");
|
|
348
|
+
await realFs.promises.writeFile(main, "import './test-output/runtime.js'; import.meta.hot.accept(); import.meta.hot.accept('./test-output/runtime.js', () => {});");
|
|
349
|
+
await server.transformRequest("/main.js");
|
|
350
|
+
await server.transformRequest("/test-output/runtime.js");
|
|
351
|
+
const client = await connectViteClient(server);
|
|
352
|
+
server.watcher.emit("change", main);
|
|
353
|
+
await vi.waitFor(() => expect(client.messages.some((message) => message.type === "update")).toBe(true));
|
|
354
|
+
await server.transformRequest("/main.js");
|
|
355
|
+
const configFile = path.join(projectDir, "paraglide.config.js");
|
|
356
|
+
const config = await realFs.promises.readFile(configFile, "utf8");
|
|
357
|
+
await realFs.promises.writeFile(configFile, "export default { outdir: 42 }");
|
|
358
|
+
await server.pluginContainer.watchChange(configFile, { event: "update" });
|
|
359
|
+
await vi.waitFor(() => expect(client.messages.some((message) => message.type === "error")).toBe(true));
|
|
360
|
+
const runtimeFile = path.join(outdir, "runtime.js");
|
|
361
|
+
const outputBefore = await realFs.promises.readFile(runtimeFile, "utf8");
|
|
362
|
+
const settings = JSON.parse(original);
|
|
363
|
+
settings.locales.push("fr");
|
|
364
|
+
await realFs.promises.writeFile(file, JSON.stringify(settings));
|
|
365
|
+
await server.pluginContainer.watchChange(file, { event: "update" });
|
|
366
|
+
const outputAfter = await realFs.promises.readFile(runtimeFile, "utf8");
|
|
367
|
+
// The fixture disables file watching; deliver the event a changed output produces.
|
|
368
|
+
if (outputAfter !== outputBefore) {
|
|
369
|
+
server.watcher.emit("change", runtimeFile);
|
|
370
|
+
await vi.waitFor(() => expect(client.messages.filter((message) => message.type === "update")).toHaveLength(2));
|
|
371
|
+
}
|
|
372
|
+
server.ws.send({ type: "custom", event: "paraglide:input-processed" });
|
|
373
|
+
await vi.waitFor(() => expect(client.messages).toContainEqual({
|
|
374
|
+
type: "custom",
|
|
375
|
+
event: "paraglide:input-processed",
|
|
376
|
+
}));
|
|
377
|
+
expect(client.messages
|
|
378
|
+
.filter((message) => ["update", "error", "full-reload"].includes(message.type))
|
|
379
|
+
.at(-1)?.type).toBe("error");
|
|
380
|
+
expect(outputAfter).toBe(outputBefore);
|
|
381
|
+
expect((await fetch(server.resolvedUrls.local[0])).status).toBe(200);
|
|
382
|
+
await realFs.promises.writeFile(configFile, config);
|
|
383
|
+
await server.pluginContainer.watchChange(configFile, { event: "update" });
|
|
384
|
+
await vi.waitFor(() => expect(client.messages).toContainEqual({ type: "full-reload" }));
|
|
385
|
+
expect(await realFs.promises.readFile(runtimeFile, "utf8")).not.toBe(outputBefore);
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
await server.close();
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
test("vite error overlay still reports failures after restarting an inline plugin", async () => {
|
|
392
|
+
const { server, file } = await startOverlayServer();
|
|
393
|
+
try {
|
|
394
|
+
await server.restart();
|
|
395
|
+
const client = await connectViteClient(server);
|
|
396
|
+
await realFs.promises.writeFile(file, "{");
|
|
397
|
+
await server.pluginContainer.watchChange(file, { event: "update" });
|
|
398
|
+
await vi.waitFor(() => expect(client.messages.some((message) => message.type === "error")).toBe(true));
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
await server.close();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
test("vite error overlay retains errors without buffering them in Vite", async () => {
|
|
405
|
+
process.env.NODE_ENV = "development";
|
|
406
|
+
const { projectDir, outdir } = await createProjectFixture();
|
|
407
|
+
const file = path.join(projectDir, "settings.json");
|
|
408
|
+
const original = await realFs.promises.readFile(file, "utf8");
|
|
409
|
+
await realFs.promises.writeFile(file, "{");
|
|
410
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
411
|
+
const plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
412
|
+
const ws = configureViteServer(plugin);
|
|
413
|
+
ws.clients.clear();
|
|
414
|
+
await plugin.buildStart.call(mockContext);
|
|
415
|
+
expect(ws.send).not.toHaveBeenCalled();
|
|
416
|
+
expect(ws.on).toHaveBeenCalledWith("connection", expect.any(Function));
|
|
417
|
+
const onConnection = ws.on.mock.calls[0][1];
|
|
418
|
+
const socket = { send: vi.fn() };
|
|
419
|
+
onConnection(socket);
|
|
420
|
+
expect(JSON.parse(socket.send.mock.calls[0][0])).toMatchObject({
|
|
421
|
+
type: "error",
|
|
422
|
+
err: { message: expect.any(String), plugin: "unplugin-paraglide-js" },
|
|
423
|
+
});
|
|
424
|
+
await realFs.promises.writeFile(file, original);
|
|
425
|
+
await plugin.watchChange.call(mockContext, file);
|
|
426
|
+
expect(ws.send).not.toHaveBeenCalled();
|
|
427
|
+
socket.send.mockClear();
|
|
428
|
+
onConnection(socket);
|
|
429
|
+
expect(socket.send).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
test("vite error overlay preserves compiler diagnostics and clears after a successful buildStart", async () => {
|
|
432
|
+
process.env.NODE_ENV = "development";
|
|
433
|
+
const actual = await vi.importActual("../compiler/compile.js");
|
|
434
|
+
const error = new SyntaxError("Invalid message pattern in messages/en.json");
|
|
435
|
+
const compile = vi.fn(actual.compile).mockRejectedValueOnce(error);
|
|
436
|
+
vi.doMock("../compiler/compile.js", () => ({ ...actual, compile }));
|
|
437
|
+
try {
|
|
438
|
+
const { projectDir, outdir } = await createProjectFixture();
|
|
439
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
440
|
+
const plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
441
|
+
const { send } = configureViteServer(plugin);
|
|
442
|
+
await plugin.buildStart.call(mockContext);
|
|
443
|
+
expect(send).toHaveBeenCalledWith({
|
|
444
|
+
type: "error",
|
|
445
|
+
err: {
|
|
446
|
+
message: error.message,
|
|
447
|
+
stack: error.stack,
|
|
448
|
+
plugin: "unplugin-paraglide-js",
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
await plugin.buildStart.call(mockContext);
|
|
452
|
+
expect(send).toHaveBeenLastCalledWith({ type: "full-reload" });
|
|
453
|
+
await plugin.buildStart.call(mockContext);
|
|
454
|
+
expect(send).toHaveBeenCalledTimes(2);
|
|
455
|
+
}
|
|
456
|
+
finally {
|
|
457
|
+
vi.doUnmock("../compiler/compile.js");
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
test("vite error overlay reports non-Error compiler failures during watchChange", async () => {
|
|
461
|
+
process.env.NODE_ENV = "development";
|
|
462
|
+
const actual = await vi.importActual("../compiler/compile.js");
|
|
463
|
+
const compile = vi.fn(actual.compile);
|
|
464
|
+
vi.doMock("../compiler/compile.js", () => ({ ...actual, compile }));
|
|
465
|
+
try {
|
|
466
|
+
const { projectDir, outdir } = await createProjectFixture();
|
|
467
|
+
const { paraglideVitePlugin } = await import("./vite.js");
|
|
468
|
+
const plugin = paraglideVitePlugin({ project: projectDir, outdir });
|
|
469
|
+
const { send } = configureViteServer(plugin);
|
|
470
|
+
await plugin.buildStart.call(mockContext);
|
|
471
|
+
compile.mockRejectedValueOnce("Invalid translation");
|
|
472
|
+
const file = path.join(projectDir, "settings.json");
|
|
473
|
+
await plugin.watchChange.call(mockContext, file);
|
|
474
|
+
expect(send).toHaveBeenLastCalledWith({
|
|
475
|
+
type: "error",
|
|
476
|
+
err: {
|
|
477
|
+
message: "Invalid translation",
|
|
478
|
+
stack: expect.any(String),
|
|
479
|
+
plugin: "unplugin-paraglide-js",
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
await plugin.watchChange.call(mockContext, file);
|
|
483
|
+
expect(send).toHaveBeenLastCalledWith({ type: "full-reload" });
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
vi.doUnmock("../compiler/compile.js");
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
test("vite error overlay keeps serving requests while the dev watcher recovers", async () => {
|
|
490
|
+
const { server, file, original } = await startOverlayServer();
|
|
491
|
+
try {
|
|
492
|
+
const send = vi.spyOn(server.ws, "send");
|
|
493
|
+
const client = await connectViteClient(server);
|
|
494
|
+
const url = server.resolvedUrls.local[0];
|
|
495
|
+
await realFs.promises.writeFile(file, "{");
|
|
496
|
+
server.watcher.emit("change", file);
|
|
497
|
+
await vi.waitFor(() => {
|
|
498
|
+
expect(send).toHaveBeenCalledWith({
|
|
499
|
+
type: "error",
|
|
500
|
+
err: expect.objectContaining({ message: expect.any(String) }),
|
|
501
|
+
});
|
|
502
|
+
}, { timeout: 10000 });
|
|
503
|
+
await vi.waitFor(() => expect(client.messages.some((message) => message.type === "error")).toBe(true));
|
|
504
|
+
const response = await fetch(url);
|
|
505
|
+
expect(response.status).toBe(200);
|
|
506
|
+
expect(await response.text()).toContain("Translation preview");
|
|
507
|
+
send.mockClear();
|
|
508
|
+
await realFs.promises.writeFile(file, original);
|
|
509
|
+
server.watcher.emit("change", file);
|
|
510
|
+
await vi.waitFor(() => expect(send).toHaveBeenCalledWith({ type: "full-reload" }), { timeout: 10000 });
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
await server.close();
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
// Vite 7 and earlier can retain a buffered error after a full reload.
|
|
517
|
+
test("vite error overlay does not replay an error recovered before the first connection", async () => {
|
|
518
|
+
const { server, file, original, outdir } = await startOverlayServer(true);
|
|
519
|
+
try {
|
|
520
|
+
await realFs.promises.writeFile(file, original);
|
|
521
|
+
await server.pluginContainer.watchChange(file, { event: "update" });
|
|
522
|
+
expect(await realFs.promises.readdir(outdir)).toContain("runtime.js");
|
|
523
|
+
const client = await connectViteClient(server);
|
|
524
|
+
expect(client.messages.some((message) => message.type === "error")).toBe(false);
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
await server.close();
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
test("vite error overlay reaches new tabs and reconnecting clients after a startup failure", async () => {
|
|
531
|
+
const { server, file, original } = await startOverlayServer(true);
|
|
532
|
+
try {
|
|
533
|
+
const first = await connectViteClient(server);
|
|
534
|
+
await vi.waitFor(() => {
|
|
535
|
+
expect(first.messages.some((message) => message.type === "error")).toBe(true);
|
|
536
|
+
});
|
|
537
|
+
const second = await connectViteClient(server);
|
|
538
|
+
expect(second.messages.some((message) => message.type === "error")).toBe(true);
|
|
539
|
+
await Promise.all([first, second].map(({ socket }) => new Promise((resolve) => {
|
|
540
|
+
socket.addEventListener("close", () => resolve(), { once: true });
|
|
541
|
+
socket.close();
|
|
542
|
+
})));
|
|
543
|
+
const reconnected = await connectViteClient(server);
|
|
544
|
+
expect(reconnected.messages.some((message) => message.type === "error")).toBe(true);
|
|
545
|
+
await realFs.promises.writeFile(file, original);
|
|
546
|
+
await server.pluginContainer.watchChange(file, { event: "update" });
|
|
547
|
+
await vi.waitFor(() => {
|
|
548
|
+
expect(reconnected.messages).toContainEqual({ type: "full-reload" });
|
|
549
|
+
});
|
|
550
|
+
const recovered = await connectViteClient(server);
|
|
551
|
+
expect(recovered.messages.some((message) => message.type === "error")).toBe(false);
|
|
552
|
+
}
|
|
553
|
+
finally {
|
|
554
|
+
await server.close();
|
|
555
|
+
}
|
|
556
|
+
});
|
|
70
557
|
test("watch mode follows config precedence flips", async () => {
|
|
71
558
|
process.env.NODE_ENV = "development";
|
|
72
559
|
const { writeFile, rm } = await import("node:fs/promises");
|
|
@@ -111,7 +598,9 @@ test("vite plugin throws on compilation errors at build time", async () => {
|
|
|
111
598
|
outdir: path.join(workspace, "out"),
|
|
112
599
|
});
|
|
113
600
|
// In production mode - should throw the error
|
|
601
|
+
const { send } = configureViteServer(plugin);
|
|
114
602
|
await expect(plugin.buildStart?.call(mockContext)).rejects.toThrow();
|
|
603
|
+
expect(send).not.toHaveBeenCalled();
|
|
115
604
|
});
|
|
116
605
|
// Regression test for https://github.com/opral/inlang-paraglide-js/issues/659:
|
|
117
606
|
// the bundler plugins used to wipe `outdir` on every fresh process, racing
|