diff --git a/bun.lock b/bun.lock index ec313d7..fe37c6d 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,6 @@ "dependencies": { "@fontsource-variable/geist": "^5.2.8", "@fontsource-variable/geist-mono": "^5.2.7", - "@hono/node-server": "^1.19.11", "axios": "^1.13.5", "commander": "^14.0.3", "discord-rpc": "^4.0.1", @@ -110,8 +109,6 @@ "@fontsource-variable/geist-mono": ["@fontsource-variable/geist-mono@5.2.7", "", {}, "sha512-ZKlZ5sjtalb2TwXKs400mAGDlt/+2ENLNySPx0wTz3bP3mWARCsUW+rpxzZc7e05d2qGch70pItt3K4qttbIYA=="], - "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], diff --git a/package.json b/package.json index 8820663..e2364fd 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "dependencies": { "@fontsource-variable/geist": "^5.2.8", "@fontsource-variable/geist-mono": "^5.2.7", - "@hono/node-server": "^1.19.11", "axios": "^1.13.5", "commander": "^14.0.3", "discord-rpc": "^4.0.1", diff --git a/src/core/services/__tests__/stats-server.test.ts b/src/core/services/__tests__/stats-server.test.ts index 721ddc6..7d2e588 100644 --- a/src/core/services/__tests__/stats-server.test.ts +++ b/src/core/services/__tests__/stats-server.test.ts @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { createStatsApp } from '../stats-server.js'; +import { createStatsApp, startStatsServer } from '../stats-server.js'; import type { ImmersionTrackerService } from '../immersion-tracker-service.js'; const SESSION_SUMMARIES = [ @@ -1110,4 +1110,54 @@ describe('stats server API routes', () => { assert.equal(res.headers.get('content-type'), 'image/jpeg'); assert.equal(ensureCalls, 1); }); + + it('starts the stats server with Bun.serve', () => { + type BunRuntime = { + Bun: { + serve: (options: { fetch: unknown; port: number; hostname: string }) => { + stop: () => void; + }; + }; + }; + + const bun = globalThis as typeof globalThis & BunRuntime; + const originalServe = bun.Bun.serve; + let servedWith: { fetch: unknown; port: number; hostname: string } | null = null; + let stopCalls = 0; + + bun.Bun.serve = (options: { fetch: unknown; port: number; hostname: string }) => { + servedWith = options; + return { + stop: () => { + stopCalls += 1; + }, + }; + }; + + try { + const server = startStatsServer({ + port: 3210, + staticDir: fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-stats-server-start-')), + tracker: createMockTracker(), + }); + + if (servedWith === null) { + throw new Error('expected Bun.serve to be called'); + } + + const servedOptions = servedWith as { + fetch: unknown; + port: number; + hostname: string; + }; + assert.equal(servedOptions.port, 3210); + assert.equal(servedOptions.hostname, '127.0.0.1'); + assert.equal(typeof servedOptions.fetch, 'function'); + + server.close(); + assert.equal(stopCalls, 1); + } finally { + bun.Bun.serve = originalServe; + } + }); }); diff --git a/src/core/services/stats-server.ts b/src/core/services/stats-server.ts index e2cbb46..2e2964c 100644 --- a/src/core/services/stats-server.ts +++ b/src/core/services/stats-server.ts @@ -1,5 +1,4 @@ import { Hono } from 'hono'; -import { serve } from '@hono/node-server'; import type { ImmersionTrackerService } from './immersion-tracker-service.js'; import { basename, extname, resolve, sep } from 'node:path'; import { readFileSync, existsSync, statSync } from 'node:fs'; @@ -156,6 +155,22 @@ export interface StatsServerConfig { resolveAnkiNoteId?: (noteId: number) => number; } +type StatsServerHandle = { + stop: () => void; +}; + +type StatsApp = ReturnType; + +type BunRuntime = { + Bun: { + serve: (options: { + fetch: StatsApp['fetch']; + port: number; + hostname: string; + }) => StatsServerHandle; + }; +}; + const STATS_STATIC_CONTENT_TYPES: Record = { '.css': 'text/css; charset=utf-8', '.gif': 'image/gif', @@ -1001,7 +1016,7 @@ export function startStatsServer(config: StatsServerConfig): { close: () => void resolveAnkiNoteId: config.resolveAnkiNoteId, }); - const server = serve({ + const server = (globalThis as typeof globalThis & BunRuntime).Bun.serve({ fetch: app.fetch, port: config.port, hostname: '127.0.0.1', @@ -1009,7 +1024,7 @@ export function startStatsServer(config: StatsServerConfig): { close: () => void return { close: () => { - server.close(); + server.stop(); }, }; }