@kodelyth/diffs 2026.5.42 → 2026.6.1
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/package.json +18 -1
- package/api.ts +0 -10
- package/assets/viewer-runtime.js +0 -21390
- package/index.ts +0 -11
- package/runtime-api.ts +0 -1
- package/src/browser.test.ts +0 -686
- package/src/browser.ts +0 -564
- package/src/config.test.ts +0 -573
- package/src/config.ts +0 -443
- package/src/http.ts +0 -324
- package/src/language-hints.test.ts +0 -156
- package/src/language-hints.ts +0 -117
- package/src/manifest.test.ts +0 -16
- package/src/pierre-themes.ts +0 -59
- package/src/plugin.ts +0 -67
- package/src/prompt-guidance.ts +0 -7
- package/src/render-target.test.ts +0 -132
- package/src/render.test.ts +0 -219
- package/src/render.ts +0 -557
- package/src/store.test.ts +0 -462
- package/src/store.ts +0 -387
- package/src/test-helpers.ts +0 -30
- package/src/tool-render-output.test.ts +0 -107
- package/src/tool.test.ts +0 -646
- package/src/tool.ts +0 -547
- package/src/types.ts +0 -127
- package/src/url.ts +0 -60
- package/src/viewer-assets.ts +0 -103
- package/src/viewer-client.ts +0 -353
- package/src/viewer-payload.ts +0 -94
- package/tsconfig.json +0 -16
package/src/config.test.ts
DELETED
|
@@ -1,573 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import AjvPkg from "ajv";
|
|
5
|
-
import type { JsonSchemaObject } from "klaw/plugin-sdk/config-schema";
|
|
6
|
-
import { describe, expect, it, vi } from "vitest";
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_DIFFS_PLUGIN_SECURITY,
|
|
9
|
-
DEFAULT_DIFFS_TOOL_DEFAULTS,
|
|
10
|
-
diffsPluginConfigSchema,
|
|
11
|
-
resolveDiffImageRenderOptions,
|
|
12
|
-
resolveDiffsPluginDefaults,
|
|
13
|
-
resolveDiffsPluginSecurity,
|
|
14
|
-
resolveDiffsPluginViewerBaseUrl,
|
|
15
|
-
} from "./config.js";
|
|
16
|
-
import { buildViewerUrl, normalizeViewerBaseUrl } from "./url.js";
|
|
17
|
-
import {
|
|
18
|
-
getServedViewerAsset,
|
|
19
|
-
resolveViewerRuntimeFileUrl,
|
|
20
|
-
VIEWER_LOADER_PATH,
|
|
21
|
-
VIEWER_RUNTIME_PATH,
|
|
22
|
-
} from "./viewer-assets.js";
|
|
23
|
-
import { parseViewerPayloadJson } from "./viewer-payload.js";
|
|
24
|
-
|
|
25
|
-
const FULL_DEFAULTS = {
|
|
26
|
-
fontFamily: "JetBrains Mono",
|
|
27
|
-
fontSize: 17,
|
|
28
|
-
lineSpacing: 1.8,
|
|
29
|
-
layout: "split",
|
|
30
|
-
showLineNumbers: false,
|
|
31
|
-
diffIndicators: "classic",
|
|
32
|
-
wordWrap: false,
|
|
33
|
-
background: false,
|
|
34
|
-
theme: "light",
|
|
35
|
-
fileFormat: "pdf",
|
|
36
|
-
fileQuality: "hq",
|
|
37
|
-
fileScale: 2.6,
|
|
38
|
-
fileMaxWidth: 1280,
|
|
39
|
-
mode: "file",
|
|
40
|
-
ttlSeconds: 21_600,
|
|
41
|
-
} as const;
|
|
42
|
-
|
|
43
|
-
function compileManifestConfigSchema() {
|
|
44
|
-
const manifest = JSON.parse(
|
|
45
|
-
fs.readFileSync(new URL("../klaw.plugin.json", import.meta.url), "utf8"),
|
|
46
|
-
) as { configSchema: JsonSchemaObject };
|
|
47
|
-
const Ajv = AjvPkg as unknown as new (opts?: object) => import("ajv").default;
|
|
48
|
-
const ajv = new Ajv({ allErrors: true, strict: false, useDefaults: true });
|
|
49
|
-
return ajv.compile(manifest.configSchema);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
|
53
|
-
if (!value || typeof value !== "object") {
|
|
54
|
-
throw new Error(`expected ${label}`);
|
|
55
|
-
}
|
|
56
|
-
return value as Record<string, unknown>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function expectFields(value: unknown, fields: Record<string, unknown>) {
|
|
60
|
-
const record = requireRecord(value, "record");
|
|
61
|
-
for (const [key, expected] of Object.entries(fields)) {
|
|
62
|
-
expect(record[key]).toEqual(expected);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe("resolveDiffsPluginDefaults", () => {
|
|
67
|
-
it("returns built-in defaults when config is missing", () => {
|
|
68
|
-
expect(resolveDiffsPluginDefaults(undefined)).toEqual(DEFAULT_DIFFS_TOOL_DEFAULTS);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("applies configured defaults from plugin config", () => {
|
|
72
|
-
expect(
|
|
73
|
-
resolveDiffsPluginDefaults({
|
|
74
|
-
defaults: FULL_DEFAULTS,
|
|
75
|
-
}),
|
|
76
|
-
).toEqual(FULL_DEFAULTS);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("clamps and falls back for invalid line spacing and indicators", () => {
|
|
80
|
-
expectFields(
|
|
81
|
-
resolveDiffsPluginDefaults({
|
|
82
|
-
defaults: {
|
|
83
|
-
lineSpacing: -5,
|
|
84
|
-
diffIndicators: "unknown",
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
{
|
|
88
|
-
lineSpacing: 1,
|
|
89
|
-
diffIndicators: "bars",
|
|
90
|
-
},
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
expectFields(
|
|
94
|
-
resolveDiffsPluginDefaults({
|
|
95
|
-
defaults: {
|
|
96
|
-
lineSpacing: 9,
|
|
97
|
-
},
|
|
98
|
-
}),
|
|
99
|
-
{
|
|
100
|
-
lineSpacing: 3,
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
expectFields(
|
|
105
|
-
resolveDiffsPluginDefaults({
|
|
106
|
-
defaults: {
|
|
107
|
-
lineSpacing: Number.NaN,
|
|
108
|
-
},
|
|
109
|
-
}),
|
|
110
|
-
{
|
|
111
|
-
lineSpacing: DEFAULT_DIFFS_TOOL_DEFAULTS.lineSpacing,
|
|
112
|
-
},
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("derives file defaults from quality preset and clamps explicit overrides", () => {
|
|
117
|
-
expectFields(
|
|
118
|
-
resolveDiffsPluginDefaults({
|
|
119
|
-
defaults: {
|
|
120
|
-
fileQuality: "print",
|
|
121
|
-
},
|
|
122
|
-
}),
|
|
123
|
-
{
|
|
124
|
-
fileQuality: "print",
|
|
125
|
-
fileScale: 3,
|
|
126
|
-
fileMaxWidth: 1400,
|
|
127
|
-
},
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
expectFields(
|
|
131
|
-
resolveDiffsPluginDefaults({
|
|
132
|
-
defaults: {
|
|
133
|
-
fileQuality: "hq",
|
|
134
|
-
fileScale: 99,
|
|
135
|
-
fileMaxWidth: 99999,
|
|
136
|
-
},
|
|
137
|
-
}),
|
|
138
|
-
{
|
|
139
|
-
fileQuality: "hq",
|
|
140
|
-
fileScale: 4,
|
|
141
|
-
fileMaxWidth: 2400,
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("falls back to png for invalid file format defaults", () => {
|
|
147
|
-
expectFields(
|
|
148
|
-
resolveDiffsPluginDefaults({
|
|
149
|
-
defaults: {
|
|
150
|
-
fileFormat: "invalid" as "png",
|
|
151
|
-
},
|
|
152
|
-
}),
|
|
153
|
-
{
|
|
154
|
-
fileFormat: "png",
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("resolves file render format from defaults and explicit overrides", () => {
|
|
160
|
-
const defaults = resolveDiffsPluginDefaults({
|
|
161
|
-
defaults: {
|
|
162
|
-
fileFormat: "pdf",
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
expect(resolveDiffImageRenderOptions({ defaults }).format).toBe("pdf");
|
|
167
|
-
expect(resolveDiffImageRenderOptions({ defaults, fileFormat: "png" }).format).toBe("png");
|
|
168
|
-
expect(resolveDiffImageRenderOptions({ defaults, format: "png" }).format).toBe("png");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("accepts format as a config alias for fileFormat", () => {
|
|
172
|
-
expectFields(
|
|
173
|
-
resolveDiffsPluginDefaults({
|
|
174
|
-
defaults: {
|
|
175
|
-
format: "pdf",
|
|
176
|
-
},
|
|
177
|
-
}),
|
|
178
|
-
{
|
|
179
|
-
fileFormat: "pdf",
|
|
180
|
-
},
|
|
181
|
-
);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("accepts image* config aliases for backward compatibility", () => {
|
|
185
|
-
expectFields(
|
|
186
|
-
resolveDiffsPluginDefaults({
|
|
187
|
-
defaults: {
|
|
188
|
-
imageFormat: "pdf",
|
|
189
|
-
imageQuality: "hq",
|
|
190
|
-
imageScale: 2.2,
|
|
191
|
-
imageMaxWidth: 1024,
|
|
192
|
-
},
|
|
193
|
-
}),
|
|
194
|
-
{
|
|
195
|
-
fileFormat: "pdf",
|
|
196
|
-
fileQuality: "hq",
|
|
197
|
-
fileScale: 2.2,
|
|
198
|
-
fileMaxWidth: 1024,
|
|
199
|
-
},
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("accepts plugin-wide artifact TTL defaults", () => {
|
|
204
|
-
expectFields(
|
|
205
|
-
resolveDiffsPluginDefaults({
|
|
206
|
-
defaults: {
|
|
207
|
-
ttlSeconds: 21_600,
|
|
208
|
-
},
|
|
209
|
-
}),
|
|
210
|
-
{
|
|
211
|
-
ttlSeconds: 21_600,
|
|
212
|
-
},
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
expectFields(
|
|
216
|
-
resolveDiffsPluginDefaults({
|
|
217
|
-
defaults: {
|
|
218
|
-
ttlSeconds: 99_999,
|
|
219
|
-
},
|
|
220
|
-
}),
|
|
221
|
-
{
|
|
222
|
-
ttlSeconds: 21_600,
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("keeps loader-applied schema defaults from shadowing aliases and quality-derived defaults", () => {
|
|
228
|
-
const validate = compileManifestConfigSchema();
|
|
229
|
-
|
|
230
|
-
const aliasOnly = {
|
|
231
|
-
defaults: {
|
|
232
|
-
format: "pdf",
|
|
233
|
-
imageQuality: "hq",
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
expect(validate(aliasOnly)).toBe(true);
|
|
237
|
-
expectFields(resolveDiffsPluginDefaults(aliasOnly), {
|
|
238
|
-
fileFormat: "pdf",
|
|
239
|
-
fileQuality: "hq",
|
|
240
|
-
fileScale: 2.5,
|
|
241
|
-
fileMaxWidth: 1200,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const qualityOnly = {
|
|
245
|
-
defaults: {
|
|
246
|
-
fileQuality: "hq",
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
expect(validate(qualityOnly)).toBe(true);
|
|
250
|
-
expectFields(resolveDiffsPluginDefaults(qualityOnly), {
|
|
251
|
-
fileQuality: "hq",
|
|
252
|
-
fileScale: 2.5,
|
|
253
|
-
fileMaxWidth: 1200,
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe("resolveDiffsPluginSecurity", () => {
|
|
259
|
-
it("defaults to local-only viewer access", () => {
|
|
260
|
-
expect(resolveDiffsPluginSecurity(undefined)).toEqual(DEFAULT_DIFFS_PLUGIN_SECURITY);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it("allows opt-in remote viewer access", () => {
|
|
264
|
-
expect(resolveDiffsPluginSecurity({ security: { allowRemoteViewer: true } })).toEqual({
|
|
265
|
-
allowRemoteViewer: true,
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
describe("resolveDiffsPluginViewerBaseUrl", () => {
|
|
271
|
-
it("defaults to undefined when config is missing", () => {
|
|
272
|
-
expect(resolveDiffsPluginViewerBaseUrl(undefined)).toBeUndefined();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it("normalizes configured viewer base URLs", () => {
|
|
276
|
-
expect(
|
|
277
|
-
resolveDiffsPluginViewerBaseUrl({
|
|
278
|
-
viewerBaseUrl: "https://example.com/klaw/",
|
|
279
|
-
}),
|
|
280
|
-
).toBe("https://example.com/klaw");
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
describe("diffs plugin schema surfaces", () => {
|
|
285
|
-
it("rejects invalid viewerBaseUrl values at manifest-validation time too", () => {
|
|
286
|
-
const validate = compileManifestConfigSchema();
|
|
287
|
-
|
|
288
|
-
expect(validate({ viewerBaseUrl: "javascript:alert(1)" })).toBe(false);
|
|
289
|
-
expect(validate({ viewerBaseUrl: "https://example.com/klaw?x=1" })).toBe(false);
|
|
290
|
-
expect(validate({ viewerBaseUrl: "https://example.com/klaw#frag" })).toBe(false);
|
|
291
|
-
expect(validate({ viewerBaseUrl: "https://example.com/klaw/" })).toBe(true);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("preserves defaults and security for direct safeParse callers", () => {
|
|
295
|
-
const parsed = requireRecord(
|
|
296
|
-
diffsPluginConfigSchema.safeParse?.({
|
|
297
|
-
viewerBaseUrl: "https://example.com/klaw/",
|
|
298
|
-
defaults: {
|
|
299
|
-
theme: "light",
|
|
300
|
-
ttlSeconds: 21_600,
|
|
301
|
-
},
|
|
302
|
-
security: {
|
|
303
|
-
allowRemoteViewer: true,
|
|
304
|
-
},
|
|
305
|
-
}),
|
|
306
|
-
"parse result",
|
|
307
|
-
);
|
|
308
|
-
expect(parsed.success).toBe(true);
|
|
309
|
-
const data = requireRecord(parsed.data, "parse data");
|
|
310
|
-
expect(data.viewerBaseUrl).toBe("https://example.com/klaw");
|
|
311
|
-
expectFields(data.defaults, {
|
|
312
|
-
fontFamily: "Fira Code",
|
|
313
|
-
fontSize: 15,
|
|
314
|
-
lineSpacing: 1.6,
|
|
315
|
-
layout: "unified",
|
|
316
|
-
showLineNumbers: true,
|
|
317
|
-
diffIndicators: "bars",
|
|
318
|
-
wordWrap: true,
|
|
319
|
-
background: true,
|
|
320
|
-
theme: "light",
|
|
321
|
-
fileFormat: "png",
|
|
322
|
-
fileQuality: "standard",
|
|
323
|
-
fileScale: 2,
|
|
324
|
-
fileMaxWidth: 960,
|
|
325
|
-
mode: "both",
|
|
326
|
-
ttlSeconds: 21_600,
|
|
327
|
-
});
|
|
328
|
-
expectFields(data.security, { allowRemoteViewer: true });
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it("canonicalizes alias-driven defaults for direct safeParse callers", () => {
|
|
332
|
-
const parsed = requireRecord(
|
|
333
|
-
diffsPluginConfigSchema.safeParse?.({
|
|
334
|
-
defaults: {
|
|
335
|
-
format: "pdf",
|
|
336
|
-
imageQuality: "hq",
|
|
337
|
-
},
|
|
338
|
-
}),
|
|
339
|
-
"parse result",
|
|
340
|
-
);
|
|
341
|
-
expect(parsed.success).toBe(true);
|
|
342
|
-
const data = requireRecord(parsed.data, "parse data");
|
|
343
|
-
expectFields(data.defaults, {
|
|
344
|
-
fileFormat: "pdf",
|
|
345
|
-
fileQuality: "hq",
|
|
346
|
-
fileScale: 2.5,
|
|
347
|
-
fileMaxWidth: 1200,
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it("rejects invalid viewerBaseUrl config values", () => {
|
|
352
|
-
const parsed = requireRecord(
|
|
353
|
-
diffsPluginConfigSchema.safeParse?.({
|
|
354
|
-
viewerBaseUrl: "javascript:alert(1)",
|
|
355
|
-
}),
|
|
356
|
-
"parse result",
|
|
357
|
-
);
|
|
358
|
-
expect(parsed.success).toBe(false);
|
|
359
|
-
const error = requireRecord(parsed.error, "parse error");
|
|
360
|
-
const issues = error.issues as Array<{ path?: unknown; message?: unknown }>;
|
|
361
|
-
expect(issues).toHaveLength(1);
|
|
362
|
-
expect(issues[0]?.path).toEqual(["viewerBaseUrl"]);
|
|
363
|
-
expect(issues[0]?.message).toBe("viewerBaseUrl must use http or https: javascript:alert(1)");
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it("keeps the runtime json schema in sync with the manifest config schema", () => {
|
|
367
|
-
const manifest = JSON.parse(
|
|
368
|
-
fs.readFileSync(new URL("../klaw.plugin.json", import.meta.url), "utf8"),
|
|
369
|
-
) as { configSchema?: unknown };
|
|
370
|
-
|
|
371
|
-
expect(diffsPluginConfigSchema.jsonSchema).toEqual(manifest.configSchema);
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
describe("diffs viewer URL helpers", () => {
|
|
376
|
-
it("defaults to loopback for lan/tailnet bind modes", () => {
|
|
377
|
-
expect(
|
|
378
|
-
buildViewerUrl({
|
|
379
|
-
config: { gateway: { bind: "lan", port: 18789 } },
|
|
380
|
-
viewerPath: "/plugins/diffs/view/id/token",
|
|
381
|
-
}),
|
|
382
|
-
).toBe("http://127.0.0.1:18789/plugins/diffs/view/id/token");
|
|
383
|
-
|
|
384
|
-
expect(
|
|
385
|
-
buildViewerUrl({
|
|
386
|
-
config: { gateway: { bind: "tailnet", port: 24444 } },
|
|
387
|
-
viewerPath: "/plugins/diffs/view/id/token",
|
|
388
|
-
}),
|
|
389
|
-
).toBe("http://127.0.0.1:24444/plugins/diffs/view/id/token");
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it("uses custom bind host when provided", () => {
|
|
393
|
-
expect(
|
|
394
|
-
buildViewerUrl({
|
|
395
|
-
config: {
|
|
396
|
-
gateway: {
|
|
397
|
-
bind: "custom",
|
|
398
|
-
customBindHost: "gateway.example.com",
|
|
399
|
-
port: 443,
|
|
400
|
-
tls: { enabled: true },
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
viewerPath: "/plugins/diffs/view/id/token",
|
|
404
|
-
}),
|
|
405
|
-
).toBe("https://gateway.example.com/plugins/diffs/view/id/token");
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it("joins viewer path under baseUrl pathname", () => {
|
|
409
|
-
expect(
|
|
410
|
-
buildViewerUrl({
|
|
411
|
-
config: {},
|
|
412
|
-
baseUrl: "https://example.com/klaw",
|
|
413
|
-
viewerPath: "/plugins/diffs/view/id/token",
|
|
414
|
-
}),
|
|
415
|
-
).toBe("https://example.com/klaw/plugins/diffs/view/id/token");
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it("prefers normalized viewerBaseUrl strings too", () => {
|
|
419
|
-
expect(
|
|
420
|
-
buildViewerUrl({
|
|
421
|
-
config: {},
|
|
422
|
-
baseUrl: "https://example.com/klaw/",
|
|
423
|
-
viewerPath: "/plugins/diffs/view/id/token",
|
|
424
|
-
}),
|
|
425
|
-
).toBe("https://example.com/klaw/plugins/diffs/view/id/token");
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it("rejects base URLs with query/hash", () => {
|
|
429
|
-
expect(() => normalizeViewerBaseUrl("https://example.com?a=1")).toThrow(
|
|
430
|
-
"baseUrl must not include query/hash",
|
|
431
|
-
);
|
|
432
|
-
expect(() => normalizeViewerBaseUrl("https://example.com#frag")).toThrow(
|
|
433
|
-
"baseUrl must not include query/hash",
|
|
434
|
-
);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
it("uses the configured field name in viewerBaseUrl validation errors", () => {
|
|
438
|
-
expect(() => normalizeViewerBaseUrl("https://example.com?a=1", "viewerBaseUrl")).toThrow(
|
|
439
|
-
"viewerBaseUrl must not include query/hash",
|
|
440
|
-
);
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
describe("viewer assets", () => {
|
|
445
|
-
it("prefers the built plugin asset layout when present", async () => {
|
|
446
|
-
const repoRoot = join(process.cwd(), "tmp", "diffs-viewer-assets-test-repo");
|
|
447
|
-
const builtRuntimePath = join(
|
|
448
|
-
repoRoot,
|
|
449
|
-
"dist",
|
|
450
|
-
"extensions",
|
|
451
|
-
"diffs",
|
|
452
|
-
"assets",
|
|
453
|
-
"viewer-runtime.js",
|
|
454
|
-
);
|
|
455
|
-
const stat = vi.fn(async (path: string) => {
|
|
456
|
-
if (path === builtRuntimePath) {
|
|
457
|
-
return { mtimeMs: 1 };
|
|
458
|
-
}
|
|
459
|
-
const error = Object.assign(new Error(`missing: ${path}`), { code: "ENOENT" });
|
|
460
|
-
throw error;
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
const runtimeUrl = await resolveViewerRuntimeFileUrl({
|
|
464
|
-
baseUrl: pathToFileURL(join(repoRoot, "dist", "extensions", "diffs", "index.js")),
|
|
465
|
-
stat,
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
expect(fileURLToPath(runtimeUrl)).toBe(builtRuntimePath);
|
|
469
|
-
expect(stat).toHaveBeenCalledTimes(1);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it("falls back to the source asset layout when the built artifact is absent", async () => {
|
|
473
|
-
const repoRoot = join(process.cwd(), "tmp", "diffs-viewer-assets-test-repo");
|
|
474
|
-
const sourceCandidatePath = join(
|
|
475
|
-
repoRoot,
|
|
476
|
-
"extensions",
|
|
477
|
-
"diffs",
|
|
478
|
-
"src",
|
|
479
|
-
"assets",
|
|
480
|
-
"viewer-runtime.js",
|
|
481
|
-
);
|
|
482
|
-
const sourceRuntimePath = join(repoRoot, "extensions", "diffs", "assets", "viewer-runtime.js");
|
|
483
|
-
const stat = vi.fn(async (path: string) => {
|
|
484
|
-
if (path === sourceRuntimePath) {
|
|
485
|
-
return { mtimeMs: 1 };
|
|
486
|
-
}
|
|
487
|
-
const error = Object.assign(new Error(`missing: ${path}`), { code: "ENOENT" });
|
|
488
|
-
throw error;
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const runtimeUrl = await resolveViewerRuntimeFileUrl({
|
|
492
|
-
baseUrl: pathToFileURL(join(repoRoot, "extensions", "diffs", "src", "viewer-assets.js")),
|
|
493
|
-
stat,
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
expect(fileURLToPath(runtimeUrl)).toBe(sourceRuntimePath);
|
|
497
|
-
expect(stat).toHaveBeenNthCalledWith(1, sourceCandidatePath);
|
|
498
|
-
expect(stat).toHaveBeenNthCalledWith(2, sourceRuntimePath);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it("serves a stable loader that points at the current runtime bundle", async () => {
|
|
502
|
-
const loader = await getServedViewerAsset(VIEWER_LOADER_PATH);
|
|
503
|
-
|
|
504
|
-
expect(loader?.contentType).toBe("text/javascript; charset=utf-8");
|
|
505
|
-
expect(String(loader?.body)).toContain(`./viewer-runtime.js?v=`);
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
it("serves the runtime bundle body", async () => {
|
|
509
|
-
const runtime = await getServedViewerAsset(VIEWER_RUNTIME_PATH);
|
|
510
|
-
|
|
511
|
-
expect(runtime?.contentType).toBe("text/javascript; charset=utf-8");
|
|
512
|
-
expect(String(runtime?.body)).toContain("klawDiffsReady");
|
|
513
|
-
expect(String(runtime?.body)).toContain('style.width="24px"');
|
|
514
|
-
expect(String(runtime?.body)).toContain('style.gap="6px"');
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
it("returns null for unknown asset paths", async () => {
|
|
518
|
-
await expect(getServedViewerAsset("/plugins/diffs/assets/not-real.js")).resolves.toBeNull();
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
describe("parseViewerPayloadJson", () => {
|
|
523
|
-
function buildValidPayload(): Record<string, unknown> {
|
|
524
|
-
return {
|
|
525
|
-
prerenderedHTML: "<div>ok</div>",
|
|
526
|
-
langs: ["text"],
|
|
527
|
-
oldFile: {
|
|
528
|
-
name: "README.md",
|
|
529
|
-
contents: "before",
|
|
530
|
-
},
|
|
531
|
-
newFile: {
|
|
532
|
-
name: "README.md",
|
|
533
|
-
contents: "after",
|
|
534
|
-
},
|
|
535
|
-
options: {
|
|
536
|
-
theme: {
|
|
537
|
-
light: "pierre-light",
|
|
538
|
-
dark: "pierre-dark",
|
|
539
|
-
},
|
|
540
|
-
diffStyle: "unified",
|
|
541
|
-
diffIndicators: "bars",
|
|
542
|
-
disableLineNumbers: false,
|
|
543
|
-
expandUnchanged: false,
|
|
544
|
-
themeType: "dark",
|
|
545
|
-
backgroundEnabled: true,
|
|
546
|
-
overflow: "wrap",
|
|
547
|
-
unsafeCSS: ":host{}",
|
|
548
|
-
},
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
it("accepts valid payload JSON", () => {
|
|
553
|
-
const parsed = parseViewerPayloadJson(JSON.stringify(buildValidPayload()));
|
|
554
|
-
expect(parsed.options.diffStyle).toBe("unified");
|
|
555
|
-
expect(parsed.options.diffIndicators).toBe("bars");
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
it("rejects payloads with invalid shape", () => {
|
|
559
|
-
const broken = buildValidPayload();
|
|
560
|
-
broken.options = {
|
|
561
|
-
...(broken.options as Record<string, unknown>),
|
|
562
|
-
diffIndicators: "invalid",
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
expect(() => parseViewerPayloadJson(JSON.stringify(broken))).toThrow(
|
|
566
|
-
"Diff payload has invalid shape.",
|
|
567
|
-
);
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it("rejects invalid JSON", () => {
|
|
571
|
-
expect(() => parseViewerPayloadJson("{not-json")).toThrow("Diff payload is not valid JSON.");
|
|
572
|
-
});
|
|
573
|
-
});
|