Move brand images to proprietary/ and support multi-dir asset pipeline (#3662)

## Description:

* Move proprietary brand images (logos, favicon) from resources/images/
to proprietary/images/ to separate open-source assets from proprietary
ones
* Extend the asset pipeline (PublicAssetManifest, vite.config.ts) to
support multiple source directories (resources/ + proprietary/), so
buildAssetUrl resolves assets from either location transparently
* In dev, serve proprietary/ as a fallback middleware (registered after
Vite's publicDir handler) so resources/ takes precedence when files
exist in both. The idea is we could have placeholder assets placeholders
that can be used by forks, and only the production build uses
proprietary assets.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
Evan
2026-04-16 08:42:11 -07:00
committed by GitHub
parent 31baeacdf4
commit 1ebac8e854
13 changed files with 100 additions and 62 deletions
-11
View File
@@ -39,17 +39,6 @@ vi.mock("howler", () => {
return { Howl: MockHowl };
});
// Mock music imports
vi.mock("../../../../proprietary/sounds/music/of4.mp3", () => ({
default: "of4.mp3",
}));
vi.mock("../../../../proprietary/sounds/music/openfront.mp3", () => ({
default: "openfront.mp3",
}));
vi.mock("../../../../proprietary/sounds/music/war.mp3", () => ({
default: "war.mp3",
}));
// Mock the Sounds module so tests don't depend on actual asset paths
vi.mock("../../../src/client/sound/Sounds", async (importOriginal) => {
const actual =
+15 -15
View File
@@ -107,11 +107,11 @@ describe("PublicAssetManifest", () => {
"utf8",
);
const firstManifest = buildPublicAssetManifest(resourcesDir);
const firstManifest = buildPublicAssetManifest([resourcesDir]);
const firstManifestHref = firstManifest["manifest.json"];
const firstIconHref = firstManifest["icons/app-icon.png"];
createHashedPublicAssetFiles(resourcesDir, outDir, firstManifest);
createHashedPublicAssetFiles([resourcesDir], outDir, firstManifest);
const firstOutput = await fs.readFile(
path.join(outDir, firstManifestHref.slice(1)),
"utf8",
@@ -124,7 +124,7 @@ describe("PublicAssetManifest", () => {
);
clearPublicAssetManifestCache();
const secondManifest = buildPublicAssetManifest(resourcesDir);
const secondManifest = buildPublicAssetManifest([resourcesDir]);
const secondManifestHref = secondManifest["manifest.json"];
const secondIconHref = secondManifest["icons/app-icon.png"];
@@ -147,8 +147,8 @@ describe("PublicAssetManifest", () => {
"utf8",
);
const assetManifest = buildPublicAssetManifest(resourcesDir);
createHashedPublicAssetFiles(resourcesDir, outDir, assetManifest);
const assetManifest = buildPublicAssetManifest([resourcesDir]);
createHashedPublicAssetFiles([resourcesDir], outDir, assetManifest);
const emittedManifest = await emitHashedAsset(
outDir,
@@ -164,7 +164,7 @@ describe("PublicAssetManifest", () => {
await writeWebManifestFixture(resourcesDir, [{ src: "icons/missing.png" }]);
expect(() => buildPublicAssetManifest(resourcesDir)).toThrow(
expect(() => buildPublicAssetManifest([resourcesDir])).toThrow(
/manifest\.json references icons\/missing\.png/i,
);
});
@@ -177,8 +177,8 @@ describe("PublicAssetManifest", () => {
{ src: "data:image/png;base64,AAA" },
]);
const assetManifest = buildPublicAssetManifest(resourcesDir);
createHashedPublicAssetFiles(resourcesDir, outDir, assetManifest);
const assetManifest = buildPublicAssetManifest([resourcesDir]);
createHashedPublicAssetFiles([resourcesDir], outDir, assetManifest);
const emittedManifest = await emitHashedAsset(
outDir,
@@ -198,8 +198,8 @@ describe("PublicAssetManifest", () => {
"test.png",
);
const assetManifest = buildPublicAssetManifest(resourcesDir);
createHashedPublicAssetFiles(resourcesDir, outDir, assetManifest);
const assetManifest = buildPublicAssetManifest([resourcesDir]);
createHashedPublicAssetFiles([resourcesDir], outDir, assetManifest);
const xmlHref = assetManifest["fonts/test.xml"];
const pngHref = assetManifest["fonts/test.png"];
@@ -220,12 +220,12 @@ describe("PublicAssetManifest", () => {
"test.png",
);
const firstManifest = buildPublicAssetManifest(resourcesDir);
const firstManifest = buildPublicAssetManifest([resourcesDir]);
await fs.writeFile(path.join(resourcesDir, "fonts", "test.png"), "png-v2");
clearPublicAssetManifestCache();
const secondManifest = buildPublicAssetManifest(resourcesDir);
const secondManifest = buildPublicAssetManifest([resourcesDir]);
expect(firstManifest["fonts/test.png"]).not.toBe(
secondManifest["fonts/test.png"],
@@ -250,7 +250,7 @@ describe("PublicAssetManifest", () => {
].join("\n"),
);
expect(() => buildPublicAssetManifest(resourcesDir)).toThrow(
expect(() => buildPublicAssetManifest([resourcesDir])).toThrow(
/missing from the asset manifest/i,
);
});
@@ -265,8 +265,8 @@ describe("PublicAssetManifest", () => {
"nested-png",
);
const assetManifest = buildPublicAssetManifest(resourcesDir);
createHashedPublicAssetFiles(resourcesDir, outDir, assetManifest);
const assetManifest = buildPublicAssetManifest([resourcesDir]);
createHashedPublicAssetFiles([resourcesDir], outDir, assetManifest);
const xmlHref = assetManifest["fonts/nested/atlas.xml"];
const pngHref = assetManifest["fonts/nested/pages/p0.png"];