diff --git a/.gitignore b/.gitignore index 3a67014..6c8ee1a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ next-env.d.ts /media *storybook.log + +# Generated per-site favicon (copied from sites//icon.ico by scripts/copy-favicon.mjs) +/src/app/(home)/icon.ico diff --git a/package.json b/package.json index 88f6861..d9e3bbd 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,11 @@ "license": "MIT", "type": "module", "scripts": { + "prebuild": "node --env-file-if-exists=.env scripts/copy-favicon.mjs", "build": "cross-env NODE_OPTIONS=--no-deprecation next build", + "predev": "node --env-file-if-exists=.env scripts/copy-favicon.mjs", "dev": "cross-env NODE_OPTIONS=--no-deprecation next dev", - "devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev", + "devsafe": "rm -rf .next && node --env-file-if-exists=.env scripts/copy-favicon.mjs && cross-env NODE_OPTIONS=--no-deprecation next dev", "generate:types": "payload generate:types", "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload", diff --git a/scripts/copy-favicon.mjs b/scripts/copy-favicon.mjs new file mode 100644 index 0000000..f2c7418 --- /dev/null +++ b/scripts/copy-favicon.mjs @@ -0,0 +1,34 @@ +// White-labeling glue: each client under sites// ships its own icon.ico. +// Next.js's App Router picks up the favicon via the file convention at +// src/app/(home)/icon.ico, which can only point to one file. This script runs +// before `next dev` / `next build` (see predev/prebuild in package.json) and +// copies the right per-site icon into that location based on NEXT_PUBLIC_SITE_ID. +// The destination is gitignored so it's treated as a build artifact. + +import { copyFileSync, existsSync, readdirSync, statSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const repoRoot = resolve(__dirname, '..') + +// Mirror the default in src/config/site.ts so an unset env var produces the +// same site here as it does at runtime. +const siteId = process.env.NEXT_PUBLIC_SITE_ID || 'dreikoenige' +const sitesDir = join(repoRoot, 'sites') +const source = join(sitesDir, siteId, 'icon.ico') +const destination = join(repoRoot, 'src', 'app', '(home)', 'icon.ico') + +// Fail loudly with the list of valid site ids — same UX as the runtime check +// in src/config/site.ts when an unknown NEXT_PUBLIC_SITE_ID is supplied. +if (!existsSync(source)) { + const available = readdirSync(sitesDir) + .filter((entry) => statSync(join(sitesDir, entry)).isDirectory()) + .join(', ') + throw new Error( + `[copy-favicon] No icon.ico for site "${siteId}" at ${source}. Available sites: ${available}`, + ) +} + +copyFileSync(source, destination) +console.log(`[copy-favicon] ${siteId} → src/app/(home)/icon.ico`) diff --git a/sites/chemnitz/icon.ico b/sites/chemnitz/icon.ico new file mode 100644 index 0000000..e123d08 Binary files /dev/null and b/sites/chemnitz/icon.ico differ diff --git a/src/app/(home)/icon.ico b/src/app/(home)/icon.ico deleted file mode 100644 index 3b0d1c7..0000000 Binary files a/src/app/(home)/icon.ico and /dev/null differ