fuzzyworld

nuts / files / vite.config.ts

vite.config.ts

Blame: the commit that last changed each line · file · history

f1262fd9 2026-04-12 Pavel Safronov1import { defineConfig, type Plugin } from 'vite'
44eb5832 2026-04-12 Pavel Safronov2import { qrcode } from 'vite-plugin-qrcode'
f1262fd9 2026-04-12 Pavel Safronov3import basicSsl from '@vitejs/plugin-basic-ssl'
4import type { IncomingMessage, ServerResponse } from 'node:http'
aa6e5a99 2026-04-13 Pavel Safronov5import { execFileSync } from 'node:child_process'
6import { readdirSync, readFileSync, existsSync, mkdirSync } from 'node:fs'
7import { join, dirname } from 'node:path'
8import { createRequire } from 'node:module'
9import { tmpdir } from 'node:os'
cf658d5b 2026-10-27 Pavel Safronov10import fuzzyworld from 'fuzzyworld-tools/vite'
aa6e5a99 2026-04-13 Pavel Safronov11
12const require = createRequire(import.meta.url)
13// eslint-disable-next-line @typescript-eslint/no-require-imports
14const ffmpeg: string = require('ffmpeg-static')
15
16const SOUNDS_SRC = new URL('./src/sounds', import.meta.url).pathname
17
18// The specific files sound.ts references (category prefix only, 000–002 appended)
19const SOUND_CATEGORIES = [
20 'impactWood_medium',
21 'impactGlass_medium',
22 'impactMetal_heavy',
23 'impactMining',
24 'impactSoft_heavy',
25 'impactSoft_medium',
26 'impactPunch_heavy',
27 'impactTin_medium',
28]
29
30function oggToMp3(src: string, dest: string): void {
31 mkdirSync(dirname(dest), { recursive: true })
32 execFileSync(ffmpeg, ['-i', src, '-q:a', '4', '-y', dest], { stdio: 'pipe' })
33}
34
35function soundFiles(): string[] {
36 return SOUND_CATEGORIES.flatMap(cat =>
37 [0, 1, 2].map(v => `${cat}_${String(v).padStart(3, '0')}`)
38 )
39}
40
41function sounds(): Plugin {
42 // Temp dir for dev-server cache
43 const cache = join(tmpdir(), 'nuts-sounds')
44 mkdirSync(cache, { recursive: true })
45
46 return {
47 name: 'sounds',
48
49 // Build: convert + emit each MP3 as an asset
50 async generateBundle() {
51 for (const name of soundFiles()) {
52 const src = join(SOUNDS_SRC, `${name}.ogg`)
53 if (!existsSync(src)) continue
54 const tmp = join(cache, `${name}.mp3`)
55 if (!existsSync(tmp)) oggToMp3(src, tmp)
56 this.emitFile({
57 type: 'asset',
58 fileName: `sounds/${name}.mp3`,
59 source: readFileSync(tmp),
60 })
61 }
62 },
63
64 // Dev: convert on first request, then serve from cache
65 configureServer(server) {
66 server.middlewares.use('/sounds/', (req: IncomingMessage, res: ServerResponse) => {
67 const mp3Name = (req.url ?? '').replace(/^\//, '')
68 if (!mp3Name.endsWith('.mp3')) { res.writeHead(404).end(); return }
69 const oggName = mp3Name.replace(/\.mp3$/, '.ogg')
70 const src = join(SOUNDS_SRC, oggName)
71 const dest = join(cache, mp3Name)
72 if (!existsSync(src)) { res.writeHead(404).end(); return }
73 try {
74 if (!existsSync(dest)) oggToMp3(src, dest)
75 const data = readFileSync(dest)
76 res.writeHead(200, { 'Content-Type': 'audio/mpeg', 'Content-Length': data.length })
77 res.end(data)
78 } catch (e) {
79 res.writeHead(500).end(String(e))
80 }
81 })
82 },
83 }
84}
f1262fd9 2026-04-12 Pavel Safronov85
86function remoteLog(): Plugin {
87 return {
88 name: 'remote-log',
89 configureServer(server) {
90 server.middlewares.use(
91 '/__log',
92 (req: IncomingMessage, res: ServerResponse) => {
93 if (req.method !== 'POST') {
94 res.writeHead(405).end()
95 return
96 }
97 let body = ''
98 req.setEncoding('utf8')
99 req.on('data', (chunk: string) => { body += chunk })
100 req.on('end', () => {
101 try {
102 const { level, msg } = JSON.parse(body) as { level: string; msg: string }
103 const prefix = '\x1b[36m[web]\x1b[0m'
104 if (level === 'error') {
105 server.config.logger.error(`${prefix} ${msg}`)
106 } else {
107 server.config.logger.info(`${prefix} ${msg}`)
108 }
109 } catch { /* malformed – ignore */ }
110 res.writeHead(204).end()
111 })
112 }
113 )
114 },
115 }
116}
44eb5832 2026-04-12 Pavel Safronov117
118export default defineConfig({
119 build: {
120 outDir: 'dist',
121 },
122 server: {
123 host: true, // listen on 0.0.0.0 so the phone can reach it
124 },
cf658d5b 2026-10-27 Pavel Safronov125 plugins: [fuzzyworld(), sounds(), remoteLog(), basicSsl(), qrcode()],
44eb5832 2026-04-12 Pavel Safronov126})