Diffs: preserve base paths for viewer assets

This commit is contained in:
Gustavo Madeira Santana
2026-03-30 15:28:16 -04:00
parent b96b1efc69
commit 4a6267bfe1
7 changed files with 35 additions and 10 deletions

View File

@@ -281,6 +281,8 @@ Viewer assets:
- `/plugins/diffs/assets/viewer.js`
- `/plugins/diffs/assets/viewer-runtime.js`
The viewer document resolves those assets relative to the viewer URL, so an optional `baseUrl` path prefix is preserved for both asset requests too.
URL construction behavior:
- If `baseUrl` is provided, it is used after strict validation.

View File

@@ -11,6 +11,7 @@ const SHARED_BROWSER_KEY = "__default__";
const IMAGE_SIZE_LIMIT_ERROR = "Diff frame did not render within image size limits.";
const PDF_REFERENCE_PAGE_HEIGHT_PX = 1_056;
const MAX_PDF_PAGES = 50;
const LOCAL_VIEWER_BASE_HREF = "http://127.0.0.1/plugins/diffs/view/local/local";
export type DiffScreenshotter = {
screenshotHtml(params: {
@@ -274,7 +275,7 @@ function injectBaseHref(html: string): string {
if (html.includes("<base ")) {
return html;
}
return html.replace("<head>", '<head><base href="http://127.0.0.1/" />');
return html.replace("<head>", `<head><base href="${LOCAL_VIEWER_BASE_HREF}" />`);
}
async function resolveBrowserExecutablePath(config: OpenClawConfig): Promise<string | undefined> {

View File

@@ -261,8 +261,8 @@ describe("renderDiffDocument", () => {
expect(rendered.fileCount).toBe(1);
expect(rendered.html).toContain("data-openclaw-diff-root");
expect(rendered.html).toContain("src/example.ts");
expect(rendered.html).toContain("/plugins/diffs/assets/viewer.js");
expect(rendered.imageHtml).toContain("/plugins/diffs/assets/viewer.js");
expect(rendered.html).toContain("../../assets/viewer.js");
expect(rendered.imageHtml).toContain("../../assets/viewer.js");
expect(rendered.imageHtml).toContain("max-width: 960px;");
expect(rendered.imageHtml).toContain("--diffs-font-size: 16px;");
expect(rendered.html).toContain("min-height: 100vh;");
@@ -273,6 +273,27 @@ describe("renderDiffDocument", () => {
expect(rendered.html).not.toContain("fonts.googleapis.com");
});
it("resolves viewer assets under an optional base path", async () => {
const rendered = await renderDiffDocument(
{
kind: "before_after",
before: "const value = 1;\n",
after: "const value = 2;\n",
},
{
presentation: DEFAULT_DIFFS_TOOL_DEFAULTS,
image: resolveDiffImageRenderOptions({ defaults: DEFAULT_DIFFS_TOOL_DEFAULTS }),
expandUnchanged: false,
},
);
const loaderSrc = rendered.html.match(/<script type="module" src="([^"]+)"><\/script>/)?.[1];
expect(loaderSrc).toBe("../../assets/viewer.js");
expect(
new URL(loaderSrc ?? "", "https://example.com/openclaw/plugins/diffs/view/id/token").pathname,
).toBe("/openclaw/plugins/diffs/assets/viewer.js");
});
it("renders multi-file patch input", async () => {
const patch = [
"diff --git a/a.ts b/a.ts",
@@ -399,7 +420,7 @@ describe("viewer assets", () => {
const loader = await getServedViewerAsset(VIEWER_LOADER_PATH);
expect(loader?.contentType).toBe("text/javascript; charset=utf-8");
expect(String(loader?.body)).toContain(`${VIEWER_RUNTIME_PATH}?v=`);
expect(String(loader?.body)).toContain(`./viewer-runtime.js?v=`);
});
it("serves the runtime bundle body", async () => {

View File

@@ -9,11 +9,11 @@ import type {
DiffViewerPayload,
RenderedDiffDocument,
} from "./types.js";
import { VIEWER_LOADER_PATH } from "./viewer-assets.js";
const DEFAULT_FILE_NAME = "diff.txt";
const MAX_PATCH_FILE_COUNT = 128;
const MAX_PATCH_TOTAL_LINES = 120_000;
const VIEWER_LOADER_DOCUMENT_PATH = "../../assets/viewer.js";
function escapeCssString(value: string): string {
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
@@ -296,7 +296,7 @@ function buildHtmlDocument(params: {
${params.bodyHtml}
</div>
</main>
<script type="module" src="${VIEWER_LOADER_PATH}"></script>
<script type="module" src="${VIEWER_LOADER_DOCUMENT_PATH}"></script>
</body>
</html>`;
}

View File

@@ -288,7 +288,7 @@ describe("createDiffsHttpHandler", () => {
expect(handled).toBe(true);
expect(res.statusCode).toBe(200);
expect(String(res.body)).toContain("/plugins/diffs/assets/viewer-runtime.js?v=");
expect(String(res.body)).toContain("./viewer-runtime.js?v=");
});
it("serves the shared viewer runtime asset", async () => {

View File

@@ -57,7 +57,7 @@ describe("diffs tool", () => {
const cleanupSpy = vi.spyOn(store, "scheduleCleanup");
const screenshotter = createPngScreenshotter({
assertHtml: (html) => {
expect(html).toContain("/plugins/diffs/assets/viewer.js");
expect(html).toContain("../../assets/viewer.js");
},
assertImage: (image) => {
expect(image).toMatchObject({
@@ -352,7 +352,7 @@ describe("diffs tool", () => {
it("prefers explicit tool params over configured defaults", async () => {
const screenshotter = createPngScreenshotter({
assertHtml: (html) => {
expect(html).toContain("/plugins/diffs/assets/viewer.js");
expect(html).toContain("../../assets/viewer.js");
},
assertImage: (image) => {
expect(image).toMatchObject({

View File

@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
export const VIEWER_ASSET_PREFIX = "/plugins/diffs/assets/";
export const VIEWER_LOADER_PATH = `${VIEWER_ASSET_PREFIX}viewer.js`;
export const VIEWER_RUNTIME_PATH = `${VIEWER_ASSET_PREFIX}viewer-runtime.js`;
const VIEWER_RUNTIME_RELATIVE_IMPORT_PATH = "./viewer-runtime.js";
const VIEWER_RUNTIME_FILE_URL = new URL("../assets/viewer-runtime.js", import.meta.url);
@@ -56,7 +57,7 @@ async function loadViewerAssets(): Promise<RuntimeAssetCache> {
runtimeAssetCache = {
mtimeMs: runtimeStat.mtimeMs,
runtimeBody,
loaderBody: `import "${VIEWER_RUNTIME_PATH}?v=${hash}";\n`,
loaderBody: `import "${VIEWER_RUNTIME_RELATIVE_IMPORT_PATH}?v=${hash}";\n`,
};
return runtimeAssetCache;
}