mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:20:46 +00:00
Hash web manifest from rewritten content
This commit is contained in:
@@ -43,6 +43,36 @@ function createContentHash(filePath: string): string {
|
||||
return createHash("sha256").update(content).digest("hex").slice(0, 12);
|
||||
}
|
||||
|
||||
function createStringHash(content: string): string {
|
||||
return createHash("sha256").update(content).digest("hex").slice(0, 12);
|
||||
}
|
||||
|
||||
function createHashedAssetUrl(relativePath: string, hash: string): string {
|
||||
const parsed = path.posix.parse(toPosixPath(relativePath));
|
||||
const hashedFileName = `${parsed.name}.${hash}${parsed.ext}`;
|
||||
const hashedRelativePath = path.posix.join(
|
||||
"_assets",
|
||||
parsed.dir,
|
||||
hashedFileName,
|
||||
);
|
||||
return `/${encodeAssetPath(hashedRelativePath)}`;
|
||||
}
|
||||
|
||||
function renderWebManifest(
|
||||
resourcesDir: string,
|
||||
assetManifest: AssetManifest,
|
||||
): string {
|
||||
const sourcePath = path.join(resourcesDir, "manifest.json");
|
||||
const manifest = JSON.parse(fs.readFileSync(sourcePath, "utf8")) as {
|
||||
icons?: Array<{ src?: string }>;
|
||||
};
|
||||
manifest.icons = manifest.icons?.map((icon) => ({
|
||||
...icon,
|
||||
src: buildAssetUrl(icon.src ?? "", assetManifest),
|
||||
}));
|
||||
return `${JSON.stringify(manifest, null, 2)}\n`;
|
||||
}
|
||||
|
||||
export function getResourcesDir(rootDir: string = process.cwd()): string {
|
||||
return path.join(rootDir, "resources");
|
||||
}
|
||||
@@ -86,18 +116,21 @@ export function buildPublicAssetManifest(resourcesDir: string): AssetManifest {
|
||||
|
||||
const manifest: AssetManifest = {};
|
||||
for (const relativePath of listHashedPublicAssetPaths(resourcesDir)) {
|
||||
if (relativePath === "manifest.json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const absolutePath = path.join(resourcesDir, relativePath);
|
||||
const parsed = path.posix.parse(toPosixPath(relativePath));
|
||||
const hash = createContentHash(absolutePath);
|
||||
const hashedFileName = `${parsed.name}.${hash}${parsed.ext}`;
|
||||
const hashedRelativePath = path.posix.join(
|
||||
"_assets",
|
||||
parsed.dir,
|
||||
hashedFileName,
|
||||
);
|
||||
manifest[relativePath] = `/${encodeAssetPath(hashedRelativePath)}`;
|
||||
manifest[relativePath] = createHashedAssetUrl(relativePath, hash);
|
||||
}
|
||||
|
||||
const renderedWebManifest = renderWebManifest(resourcesDir, manifest);
|
||||
manifest["manifest.json"] = createHashedAssetUrl(
|
||||
"manifest.json",
|
||||
createStringHash(renderedWebManifest),
|
||||
);
|
||||
|
||||
manifestCache.set(resourcesDir, manifest);
|
||||
return manifest;
|
||||
}
|
||||
@@ -117,14 +150,10 @@ export function createHashedPublicAssetFiles(
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
|
||||
if (relativePath === "manifest.json") {
|
||||
const manifest = JSON.parse(fs.readFileSync(sourcePath, "utf8")) as {
|
||||
icons?: Array<{ src?: string }>;
|
||||
};
|
||||
manifest.icons = manifest.icons?.map((icon) => ({
|
||||
...icon,
|
||||
src: buildAssetUrl(icon.src ?? "", assetManifest),
|
||||
}));
|
||||
fs.writeFileSync(outputPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||
fs.writeFileSync(
|
||||
outputPath,
|
||||
renderWebManifest(resourcesDir, assetManifest),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import {
|
||||
buildPublicAssetManifest,
|
||||
clearPublicAssetManifestCache,
|
||||
createHashedPublicAssetFiles,
|
||||
} from "../../src/server/PublicAssetManifest";
|
||||
|
||||
describe("PublicAssetManifest", () => {
|
||||
let tempDir: string | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
clearPublicAssetManifestCache();
|
||||
if (tempDir) {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
tempDir = null;
|
||||
}
|
||||
});
|
||||
|
||||
test("hashes manifest.json from its rewritten content", async () => {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "public-assets-"));
|
||||
const resourcesDir = path.join(tempDir, "resources");
|
||||
const outDir = path.join(tempDir, "static");
|
||||
|
||||
await fs.mkdir(path.join(resourcesDir, "icons"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(resourcesDir, "manifest.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "OpenFront",
|
||||
icons: [{ src: "icons/app-icon.png" }],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(resourcesDir, "icons", "app-icon.png"),
|
||||
"icon-v1",
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const firstManifest = buildPublicAssetManifest(resourcesDir);
|
||||
const firstManifestHref = firstManifest["manifest.json"];
|
||||
const firstIconHref = firstManifest["icons/app-icon.png"];
|
||||
|
||||
createHashedPublicAssetFiles(resourcesDir, outDir, firstManifest);
|
||||
const firstOutput = await fs.readFile(
|
||||
path.join(outDir, firstManifestHref.slice(1)),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(resourcesDir, "icons", "app-icon.png"),
|
||||
"icon-v2",
|
||||
"utf8",
|
||||
);
|
||||
clearPublicAssetManifestCache();
|
||||
|
||||
const secondManifest = buildPublicAssetManifest(resourcesDir);
|
||||
const secondManifestHref = secondManifest["manifest.json"];
|
||||
const secondIconHref = secondManifest["icons/app-icon.png"];
|
||||
|
||||
expect(firstIconHref).not.toBe(secondIconHref);
|
||||
expect(firstManifestHref).not.toBe(secondManifestHref);
|
||||
expect(firstOutput).toContain(firstIconHref);
|
||||
expect(firstOutput).not.toContain(secondIconHref);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user