webapp/pwa-assets.config.ts
Padreug 50a345ce4e feat(branding): install @vite-pwa/assets-generator + config
Adds pwa-assets.config.ts that reads $BRAND_DIR (default
./branding/default) and $BRAND_APP (optional per-standalone
override), resolves logo.svg/logo.png with documented fallback
order, and emits the existing icon set (favicon.ico,
icon-{192,512}.png, icon-maskable-{192,512}.png,
apple-touch-icon.png) into public/icons/.

Generator outputs alongside its source, so the config stages the
brand source into public/icons/.brand-source.{svg,png}; gitignoring
public/icons/ covers both staged source and generated icons in one
line.

Adds pnpm script `generate-pwa-assets`. Vite configs / HTML <link>
href updates come in follow-up commits; this commit alone produces
the icon set under public/icons/ but doesn't yet replace the
committed public/*.png binaries.

Part of aiolabs/webapp#95 (brand kit architecture).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 22:38:26 +02:00

54 lines
1.7 KiB
TypeScript

import { defineConfig } from '@vite-pwa/assets-generator/config'
import { copyFileSync, existsSync, mkdirSync } from 'node:fs'
import { join, resolve } from 'node:path'
const BRAND_DIR = process.env.BRAND_DIR ?? './branding/default'
const BRAND_APP = process.env.BRAND_APP ?? ''
const candidates: string[] = []
if (BRAND_APP) {
candidates.push(
join(BRAND_DIR, 'icons', BRAND_APP, 'logo.svg'),
join(BRAND_DIR, 'icons', BRAND_APP, 'logo.png'),
)
}
candidates.push(
join(BRAND_DIR, 'logo.svg'),
join(BRAND_DIR, 'logo.png'),
)
const source = candidates.find((p) => existsSync(p))
if (!source) {
throw new Error(
`No brand logo found. Tried:\n ${candidates.join('\n ')}\n` +
`See branding/README.md for the brand kit contract.`,
)
}
// The CLI emits next to the source. Stage into public/icons/ so generated
// PNGs are served at /icons/<name>.png and a single .gitignore line covers
// the whole tree.
const stagingDir = resolve('public/icons')
mkdirSync(stagingDir, { recursive: true })
const sourceExt = source.toLowerCase().endsWith('.svg') ? '.svg' : '.png'
const stagedSource = join(stagingDir, `.brand-source${sourceExt}`)
copyFileSync(source, stagedSource)
export default defineConfig({
headLinkOptions: { preset: '2023' },
preset: {
transparent: {
sizes: [192, 512],
favicons: [[48, 'favicon.ico']],
},
maskable: { sizes: [192, 512] },
apple: { sizes: [180] },
assetName: (type, size) => {
if (type === 'transparent') return `icon-${size.width}.png`
if (type === 'maskable') return `icon-maskable-${size.width}.png`
if (type === 'apple') return 'apple-touch-icon.png'
throw new Error(`Unknown asset type: ${type}`)
},
},
images: [stagedSource],
})