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>
54 lines
1.7 KiB
TypeScript
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],
|
|
})
|