Files
minstrel/web/vite.config.ts
T
bvandeusenandClaude Opus 5.5 b1b10b0c77
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 56s
release / go (push) Successful in 2m22s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m14s
release / Verify release artifacts (tag releases only) (push) Skipped
fix(web): run Tailwind 4 through @tailwindcss/vite, not PostCSS (#5021)
The image build on fecd030b failed in `vite build`:

  [postcss] ENOENT: no such file or directory, open '/web/tailwindcss'

With a PostCSS config present, Vite's own @import inliner resolves
`@import 'tailwindcss'` before @tailwindcss/postcss sees it, and reads it
as a relative file. svelte-check and Vitest never build CSS, so only the
image job caught it. The Vite plugin is Tailwind's documented setup for
Vite projects and handles the import itself, so postcss.config.cjs and
the direct postcss and @tailwindcss/postcss dependencies go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:55:48 -04:00

95 lines
3.6 KiB
TypeScript

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
import { execSync } from 'node:child_process';
import tokens from './src/lib/styles/tokens.json' with { type: 'json' };
const THEME_DARK = tokens.colors.dark.obsidian;
const THEME_LIGHT = tokens.colors.light.obsidian;
const tokensPlugin = {
name: 'minstrel-tokens',
buildStart() {
execSync('node scripts/tokens-to-css.js', { stdio: 'inherit' });
}
};
// Build-only plugin that injects Go html/template tokens into the
// rendered index.html so the runtime Go server can substitute per-
// instance branding. In dev (`vite dev`), the marker stays as a
// comment and the SPA falls back to the literal app.html title.
// `enforce: 'post'` ensures this runs after SvelteKit's own
// transformIndexHtml so %sveltekit.head% is already expanded into
// hydration markup before our anchors are rewritten.
const injectGoTokensPlugin = {
name: 'minstrel-inject-go-tokens',
apply: 'build' as const,
enforce: 'post' as const,
transformIndexHtml(html: string) {
const titleTag = `<title>{{ .AppName }}</title>`;
const headBlock = `
<meta name="description" content="{{ .Description }}">
<meta property="og:title" content="{{ .AppName }}">
<meta property="og:description" content="{{ .Description }}">
<meta property="og:image" content="/brand/og-image.png">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="${THEME_DARK}">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="${THEME_LIGHT}">
<script>window.__MINSTREL__ = { appName: "{{ .AppName }}" };</script>`;
// Fail loudly if either anchor drifts. A silent .replace() no-op
// would ship a build with un-substituted Go template tokens missing,
// and the runtime brand override would silently break — exactly the
// class of regression a build-time plugin should catch.
if (!html.includes('<title>Minstrel</title>')) {
throw new Error('minstrel-inject-go-tokens: <title>Minstrel</title> anchor missing from rendered index.html');
}
if (!html.includes('<!-- MINSTREL_TOKENS -->')) {
throw new Error('minstrel-inject-go-tokens: <!-- MINSTREL_TOKENS --> anchor missing from app.html');
}
return html
.replace('<title>Minstrel</title>', titleTag)
.replace('<!-- MINSTREL_TOKENS -->', headBlock);
}
};
// SvelteKit's options, which lived in svelte.config.js before kit 3. Exported
// so vitest.config.ts runs the same kit setup (the $test-utils alias above all).
export const kitOptions = {
preprocess: vitePreprocess(),
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: false,
strict: false
}),
files: { assets: 'static' },
alias: {
// test-only helper barrel; resolves the same way #lib does so test
// files can import shared mock factories without ../../../ chains.
'$test-utils': 'src/test-utils',
'$test-utils/*': 'src/test-utils/*'
}
};
export default defineConfig({
plugins: [tailwindcss(), tokensPlugin, sveltekit(kitOptions), injectGoTokensPlugin],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:4533',
changeOrigin: true
},
'/rest': {
target: 'http://localhost:4533',
changeOrigin: true
}
}
}
});