Fix stale static SPA fallback html

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-05-22 16:19:16 +00:00
1 parent 4ad8727c42
commit 678ba3335e
3 files changed
+58 -2

No files matched your search

@@ -0,0 +1,49 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import express from "express";
import request from "supertest";
import { afterEach, describe, expect, it } from "vitest";
import { readBrandedStaticIndexHtml } from "../static-index-html.js";
describe("static SPA fallback HTML", () => {
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it("serves the current index.html instead of reusing stale asset hashes", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-static-index-"));
tempDirs.push(tempDir);
const indexPath = path.join(tempDir, "index.html");
const app = express();
app.get(/.*/, (_req, res) => {
res
.status(200)
.set("Content-Type", "text/html")
.set("Cache-Control", "no-cache")
.end(readBrandedStaticIndexHtml(tempDir));
});
fs.writeFileSync(
indexPath,
'<html><body><script type="module" src="/assets/index-old.js"></script></body></html>',
"utf8",
);
await expect(request(app).get("/PAP/issues/PAP-9939")).resolves.toMatchObject({
text: expect.stringContaining("/assets/index-old.js"),
});
fs.writeFileSync(
indexPath,
'<html><body><script type="module" src="/assets/index-new.js"></script></body></html>',
"utf8",
);
const res = await request(app).get("/PAP/issues/PAP-9939");
expect(res.text).toContain("/assets/index-new.js");
expect(res.text).not.toContain("/assets/index-old.js");
});
});
+2 -2
View File
@@ -41,6 +41,7 @@ import { accessRoutes } from "./routes/access.js";
import { pluginRoutes } from "./routes/plugins.js";
import { adapterRoutes } from "./routes/adapters.js";
import { pluginUiStaticRoutes } from "./routes/plugin-ui-static.js";
import { readBrandedStaticIndexHtml } from "./static-index-html.js";
import { applyUiBranding } from "./ui-branding.js";
import { logger } from "./middleware/logger.js";
import { DEFAULT_LOCAL_PLUGIN_DIR, pluginLoader } from "./services/plugin-loader.js";
@@ -328,7 +329,6 @@ export async function createApp(
];
const uiDist = candidates.find((p) => fs.existsSync(path.join(p, "index.html")));
if (uiDist) {
const indexHtml = applyUiBranding(fs.readFileSync(path.join(uiDist, "index.html"), "utf-8"));
// Hashed asset files (Vite emits them under /assets/<name>.<hash>.<ext>)
// never change once built, so they can be cached aggressively.
app.use(
@@ -368,7 +368,7 @@ export async function createApp(
.status(200)
.set("Content-Type", "text/html")
.set("Cache-Control", "no-cache")
.end(indexHtml);
.end(readBrandedStaticIndexHtml(uiDist));
});
} else {
console.warn("[paperclip] UI dist not found; running in API-only mode");
+7
View File
@@ -0,0 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { applyUiBranding } from "./ui-branding.js";
export function readBrandedStaticIndexHtml(uiDist: string): string {
return applyUiBranding(fs.readFileSync(path.join(uiDist, "index.html"), "utf-8"));
}