feat(branding): migrate hardcoded @brand/logo.png to SVG-aware alias
The four shared layout/login components (Login, LoginDemo,
AppSidebar, MobileDrawer) hardcoded `<img src="@brand/logo.png">`,
which means an SVG-only brand kit (like cfaun's) fails the build
with "Could not load @brand/logo.png".
Switch the four to `<img src="@brand-app-logo">` — the alias registered
via brandAppLogoAliasEntry() (already used by events module) resolves
to whichever of logo.{svg,png} exists in BRAND_DIR (SVG preferred),
with per-app overrides under BRAND_DIR/icons/<app>/.
Also register brandAppLogoAliasEntry in the 8 vite configs that
didn't have it (only events did before), converting their alias maps
to array form so the regex-based logo entry doesn't get shadowed by
the bare-string `@brand` and `@` aliases.
Verified:
- AIO default brand (PNG-only): builds, ships logo-<hash>.png — no regression.
- cfaun brand (SVG-only): builds, ships logo-<hash>.svg — previous
ENOENT on logo.png gone.
Unblocks cfaun deploy with an SVG-only brand kit (no manual
rasterization needed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
80b8219494
commit
5700ac1d1a
12 changed files with 75 additions and 51 deletions
|
|
@ -48,7 +48,7 @@ const isActive = (href: string) => {
|
||||||
<div class="flex h-16 shrink-0 items-center">
|
<div class="flex h-16 shrink-0 items-center">
|
||||||
<router-link to="/" class="flex items-center gap-2">
|
<router-link to="/" class="flex items-center gap-2">
|
||||||
<img
|
<img
|
||||||
src="@brand/logo.png"
|
src="@brand-app-logo"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ const navigateTo = (href: string) => {
|
||||||
<SheetHeader class="px-6 py-4 border-b border-border">
|
<SheetHeader class="px-6 py-4 border-b border-border">
|
||||||
<SheetTitle class="flex items-center gap-2">
|
<SheetTitle class="flex items-center gap-2">
|
||||||
<img
|
<img
|
||||||
src="@brand/logo.png"
|
src="@brand-app-logo"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<!-- Logo and Title -->
|
<!-- Logo and Title -->
|
||||||
<div class="text-center space-y-6">
|
<div class="text-center space-y-6">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<img src="@brand/logo.png" alt="Logo" class="h-24 w-24 sm:h-32 sm:w-32" />
|
<img src="@brand-app-logo" alt="Logo" class="h-24 w-24 sm:h-32 sm:w-32" />
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h1 class="text-3xl font-bold tracking-tight">Virtual Realm</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Virtual Realm</h1>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<!-- Welcome Section -->
|
<!-- Welcome Section -->
|
||||||
<div class="text-center space-y-2 sm:space-y-4">
|
<div class="text-center space-y-2 sm:space-y-4">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<img src="@brand/logo.png" alt="Logo" class="h-34 w-34 sm:h-42 sm:w-42 lg:h-50 lg:w-50" />
|
<img src="@brand-app-logo" alt="Logo" class="h-34 w-34 sm:h-42 sm:w-42 lg:h-50 lg:w-50" />
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1 sm:space-y-3">
|
<div class="space-y-1 sm:space-y-3">
|
||||||
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight">Welcome to the Virtual Realm</h1>
|
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight">Welcome to the Virtual Realm</h1>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
function chatHtmlPlugin(): Plugin {
|
function chatHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -103,12 +103,15 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('chat'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
'@/app.config': fileURLToPath(new URL('./src/chat-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/chat-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-chat',
|
outDir: 'dist-chat',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig } from 'vite'
|
||||||
import Inspect from 'vite-plugin-inspect'
|
import Inspect from 'vite-plugin-inspect'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
//
|
//
|
||||||
|
|
@ -49,10 +49,13 @@ export default defineConfig(({ mode }) => ({
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare alias.
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
alias: [
|
||||||
}
|
brandAppLogoAliasEntry(),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
|
]
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
function forumHtmlPlugin(): Plugin {
|
function forumHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -103,12 +103,15 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('forum'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
'@/app.config': fileURLToPath(new URL('./src/forum-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/forum-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-forum',
|
outDir: 'dist-forum',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to rewrite dev server requests to libra.html
|
* Plugin to rewrite dev server requests to libra.html
|
||||||
|
|
@ -110,16 +110,19 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('libra'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @rollup/plugin-alias is first-match-wins.
|
// ORDER MATTERS — @rollup/plugin-alias is first-match-wins.
|
||||||
// The more specific @/app.config remap must precede the @ prefix
|
// The more specific @/app.config remap must precede the @ prefix
|
||||||
// alias, otherwise '@/app.config' matches '@' first and resolves
|
// alias, otherwise '@/app.config' matches '@' first and resolves
|
||||||
// to ./src/app.config (the hub config). ExpensesAPI etc. import
|
// to ./src/app.config (the hub config). ExpensesAPI etc. import
|
||||||
// from @/app.config and need the per-app config.
|
// from @/app.config and need the per-app config.
|
||||||
'@/app.config': fileURLToPath(new URL('./src/accounting-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/accounting-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-libra',
|
outDir: 'dist-libra',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
function marketHtmlPlugin(): Plugin {
|
function marketHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -103,12 +103,15 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('market'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
'@/app.config': fileURLToPath(new URL('./src/market-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/market-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-market',
|
outDir: 'dist-market',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
function restaurantHtmlPlugin(): Plugin {
|
function restaurantHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -110,12 +110,15 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('restaurant'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
'@/app.config': fileURLToPath(new URL('./src/restaurant-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/restaurant-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-restaurant',
|
outDir: 'dist-restaurant',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
function tasksHtmlPlugin(): Plugin {
|
function tasksHtmlPlugin(): Plugin {
|
||||||
return {
|
return {
|
||||||
|
|
@ -103,12 +103,15 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('tasks'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
'@/app.config': fileURLToPath(new URL('./src/tasks-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/tasks-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-tasks',
|
outDir: 'dist-tasks',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { defineConfig, type Plugin } from 'vite'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import { brand, brandAlias, brandAssetsPlugin } from './vite-branding'
|
import { brand, brandAlias, brandAppLogoAliasEntry, brandAssetsPlugin } from './vite-branding'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to rewrite dev server requests to wallet.html
|
* Plugin to rewrite dev server requests to wallet.html
|
||||||
|
|
@ -109,12 +109,15 @@ export default defineConfig(({ mode }) => ({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
// Array form so the per-standalone logo regex (matches `@brand-app-logo`
|
||||||
...brandAlias,
|
// with optional `?url` query) doesn't get shadowed by the bare aliases.
|
||||||
|
alias: [
|
||||||
|
brandAppLogoAliasEntry('wallet'),
|
||||||
|
...Object.entries(brandAlias).map(([find, replacement]) => ({ find, replacement })),
|
||||||
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
// ORDER MATTERS — @/app.config must precede @ (first-match-wins).
|
||||||
'@/app.config': fileURLToPath(new URL('./src/wallet-app/app.config.ts', import.meta.url)),
|
{ find: '@/app.config', replacement: fileURLToPath(new URL('./src/wallet-app/app.config.ts', import.meta.url)) },
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-wallet',
|
outDir: 'dist-wallet',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue