import {fileURLToPath, URL} from 'node:url' import {execSync} from 'node:child_process' import {existsSync} from 'node:fs' import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue' // Vite's dev-server SPA fallback only rewrites a request to index.html when the last path // segment has no dot (it sniffs for a file extension). A route whose only/last segment is a // URL-encoded handle (e.g. /groups/+user@a.localhost, see encodeHandleForUrl in src/router.js) // legitimately contains a dot from the domain part, so a direct load/refresh 404s there unless // we pre-empt that check ourselves. Gating on both a text/html Accept header and an '@' in the // last segment keeps this from ever matching real asset requests or vite's own internal // @fs/@id/@vite module paths (which never carry an html Accept header). function handleUrlFallback() { return { name: 'handle-url-fallback', configureServer(server) { server.middlewares.use((req, res, next) => { const accept = req.headers.accept || '' const pathname = (req.url || '').split('?')[0] const lastSegment = pathname.slice(pathname.lastIndexOf('/') + 1) if (req.method === 'GET' && accept.includes('text/html') && lastSegment.includes('@')) { req.url = '/index.html' } next() }) } } } function gitCommit() { // deploy/dev/docker-compose.yml bind-mounts the repo's real .git dir at // /git (separate from /app, which only has the frontend/ subtree) - // point git at it explicitly there. Bare-metal dev has no such mount, // but this file's cwd sits inside the real checkout so plain rev-parse // finds it by walking up. In prod, the build context is frontend/ alone // with no .git anywhere, so both fail and we fall back to the // GIT_COMMIT build-arg/env var (see deploy/prod/Dockerfile.frontend and // playbook.yml). const gitDirFlag = existsSync('/git') ? '--git-dir=/git ' : '' try { return execSync(`git ${gitDirFlag}rev-parse --short HEAD`).toString().trim() } catch { return process.env.GIT_COMMIT || 'unknown' } } export default defineConfig({ plugins: [vue(), handleUrlFallback()], define: { __GIT_COMMIT__: JSON.stringify(gitCommit()) }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { host: true, cors: true, headers: { //allow all origins 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Auth-Token, Authorization, Accept, charset, boundary, Content-Length', 'Access-Control-Allow-Credentials': 'true', 'Access-Control-Max-Age': '86400', //'Upgrade-Insecure-Requests': '1', 'Content-Security-Policy': 'default-src \'self\';' + ' script-src \'self\' \'wasm-unsafe-eval\' \'unsafe-eval\' \'unsafe-inline\';' // Without this, worker-src falls back to script-src, which has no blob: source - // CameraScanner.vue's decode Worker (built from a Blob URL) would be silently // blocked by CSP rather than the app's own capability checks. + ' worker-src \'self\' blob:;' + ' style-src \'self\' \'unsafe-inline\';' + ' img-src \'self\' * data: blob:;' + ' connect-src * data:', }, }, test: { include: ['src/tests/**/*.js'], globals: true, environment: "jsdom" } })