frontend-ci / lint (push) Successful in 24s
frontend-ci / test (push) Successful in 32s
frontend-ci / secrets-scan (push) Successful in 4s
frontend-ci / sast (push) Successful in 6s
frontend-ci / fs-scan (push) Successful in 11s
frontend-ci / typecheck (push) Successful in 14s
frontend-ci / build (push) Successful in 38s
frontend-ci / push (push) Skipped
frontend-ci / test (pull_request) Successful in 34s
frontend-ci / secrets-scan (pull_request) Successful in 4s
frontend-ci / sast (pull_request) Successful in 6s
frontend-ci / fs-scan (pull_request) Successful in 13s
frontend-ci / typecheck (pull_request) Successful in 23s
frontend-ci / lint (pull_request) Successful in 26s
frontend-ci / build (pull_request) Successful in 48s
frontend-ci / push (pull_request) Skipped
Enabling push in production failed with "Validation failed": the browser's PushSubscription.toJSON() carries expirationTime, and it was sent verbatim to a schema that is strict about unknown fields. The client now sends exactly endpoint + keys. The test doubles had been shaped like the schema rather than like a browser, which is why 1,112 tests said nothing. The CSP hash for index.html's inline theme script was hardcoded in the container's start-up script behind a comment asking for it to be regenerated by hand, and claiming a test asserted it; none did. The bytes are what the browser hashes, so an edit or a checkout with different line endings would have silently blocked the page's own script. The hash is now emitted by `npm run build` from the index.html that is actually shipped, copied into the image, and read at start; the container refuses to start without it. Verified against the built image: header hash equals served-script hash. For the record: a CSP console message in production is what led here, and the served page turned out to be correct - that message was a browser extension's injected script, which the policy is right to refuse.
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
/// <reference types="vitest/config" />
|
|
import { defineConfig } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import path from 'path'
|
|
|
|
// Vendor groups for manualChunks. Each changes on its own cadence, so each
|
|
// gets its own cache entry.
|
|
const REACT = new Set(['react', 'react-dom', 'scheduler', 'use-sync-external-store']);
|
|
const NET = new Set([
|
|
'axios',
|
|
'socket.io-client',
|
|
'socket.io-parser',
|
|
'engine.io-client',
|
|
'engine.io-parser',
|
|
]);
|
|
const UI = new Set(['lucide-react', 'sonner', 'tailwind-merge', 'clsx', 'class-variance-authority']);
|
|
|
|
export default defineConfig({
|
|
plugins: [react(), tailwindcss()],
|
|
resolve: {
|
|
alias: {
|
|
'@': path.resolve(__dirname, './src'),
|
|
},
|
|
},
|
|
build: {
|
|
rollupOptions: {
|
|
output: {
|
|
// Vendor code is split into chunks that change only when a dependency
|
|
// does. Without this, every deploy of app code changed the hash of the
|
|
// one chunk that also held React, the router and the UI kit, and every
|
|
// returning visitor re-downloaded ~170 KB gzipped of libraries that had
|
|
// not changed. The first load is not smaller - the dependency set is
|
|
// the load - but the second visit after a deploy fetches only the app
|
|
// chunk. Route code is split separately via React.lazy in App.tsx.
|
|
manualChunks(id) {
|
|
// Vite normalises module ids to forward slashes on every platform.
|
|
const pkg = /\/node_modules\/((?:@[^/]+\/)?[^/]+)\//.exec(id)?.[1];
|
|
if (!pkg) return undefined;
|
|
if (REACT.has(pkg)) return 'vendor-react';
|
|
if (pkg === 'react-router') return 'vendor-router';
|
|
if (NET.has(pkg)) return 'vendor-net';
|
|
if (UI.has(pkg) || pkg.startsWith('@base-ui/') || pkg.startsWith('@floating-ui/')) {
|
|
return 'vendor-ui';
|
|
}
|
|
return 'vendor';
|
|
},
|
|
},
|
|
},
|
|
},
|
|
server: {
|
|
// Bind-mounted source on Docker Desktop (Windows/macOS) does not deliver
|
|
// inotify events, so Vite never sees host edits and silently serves stale
|
|
// modules. Polling is opt-in via the compose file so native dev is unaffected.
|
|
watch: process.env.VITE_USE_POLLING
|
|
? { usePolling: true, interval: 300 }
|
|
: undefined,
|
|
proxy: {
|
|
'/api': {
|
|
target: 'http://localhost:3000',
|
|
changeOrigin: true,
|
|
},
|
|
'/socket.io': {
|
|
target: 'http://localhost:3000',
|
|
ws: true,
|
|
changeOrigin: true,
|
|
},
|
|
},
|
|
},
|
|
test: {
|
|
environment: 'jsdom',
|
|
setupFiles: ['./src/test/setup.ts'],
|
|
include: ['src/**/*.{test,spec}.{ts,tsx}', 'scripts/**/*.test.ts'],
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'json-summary'],
|
|
// Source only. Types, the entry point, generated UI primitives and the
|
|
// tests themselves are not behaviour this suite is responsible for.
|
|
include: ['src/**/*.{ts,tsx}'],
|
|
exclude: [
|
|
'src/**/*.{test,spec}.{ts,tsx}',
|
|
'src/test/**',
|
|
'src/types/**',
|
|
'src/main.tsx',
|
|
'src/vite-env.d.ts',
|
|
],
|
|
// The bar is 90 for both lines and branches. A file may be excepted
|
|
// down to 85 only where 90 is genuinely unattainable, with the reason
|
|
// recorded beside the override. See WP-14 in the audit plan.
|
|
thresholds: {
|
|
lines: 90,
|
|
branches: 90,
|
|
functions: 90,
|
|
statements: 90,
|
|
},
|
|
},
|
|
},
|
|
})
|