mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-30 06:12:06 -07:00
fix(ci): restore coverage lane compatibility
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: TASK-242
|
||||
title: Fix stats server Bun fallback in coverage lane
|
||||
status: In Progress
|
||||
assignee: []
|
||||
created_date: '2026-03-29 07:31'
|
||||
labels:
|
||||
- ci
|
||||
- bug
|
||||
milestone: cleanup
|
||||
dependencies: []
|
||||
references:
|
||||
- 'PR #36'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Coverage CI fails when `startStatsServer` reaches the Bun server seam under the maintained source lane. Add a runtime fallback that works when `Bun.serve` is unavailable and keep the stats-server startup path testable.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 `bun run test:coverage:src` passes in GitHub CI
|
||||
- [ ] #2 `startStatsServer` uses `Bun.serve` when present and a Node server fallback otherwise
|
||||
- [ ] #3 Regression coverage exists for the fallback startup path
|
||||
<!-- AC:END -->
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Hono } from 'hono';
|
||||
import { serve } from '@hono/node-server';
|
||||
import type { ImmersionTrackerService } from './immersion-tracker-service.js';
|
||||
import http, { type IncomingMessage, type ServerResponse } from 'node:http';
|
||||
import { basename, extname, resolve, sep } from 'node:path';
|
||||
import { readFileSync, existsSync, statSync } from 'node:fs';
|
||||
import { Readable } from 'node:stream';
|
||||
import { MediaGenerator } from '../../media-generator.js';
|
||||
import { AnkiConnectClient } from '../../anki-connect.js';
|
||||
import type { AnkiConnectConfig } from '../../types.js';
|
||||
@@ -60,6 +61,71 @@ function resolveStatsNoteFieldName(
|
||||
return null;
|
||||
}
|
||||
|
||||
function toFetchHeaders(headers: IncomingMessage['headers']): Headers {
|
||||
const fetchHeaders = new Headers();
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
if (value === undefined) continue;
|
||||
if (Array.isArray(value)) {
|
||||
for (const entry of value) {
|
||||
fetchHeaders.append(name, entry);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
fetchHeaders.set(name, value);
|
||||
}
|
||||
return fetchHeaders;
|
||||
}
|
||||
|
||||
function toFetchRequest(req: IncomingMessage): Request {
|
||||
const method = req.method ?? 'GET';
|
||||
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);
|
||||
const init: RequestInit & { duplex?: 'half' } = {
|
||||
method,
|
||||
headers: toFetchHeaders(req.headers),
|
||||
};
|
||||
|
||||
if (method !== 'GET' && method !== 'HEAD') {
|
||||
init.body = Readable.toWeb(req) as BodyInit;
|
||||
init.duplex = 'half';
|
||||
}
|
||||
|
||||
return new Request(url, init);
|
||||
}
|
||||
|
||||
async function writeFetchResponse(res: ServerResponse, response: Response): Promise<void> {
|
||||
res.statusCode = response.status;
|
||||
response.headers.forEach((value, key) => {
|
||||
res.setHeader(key, value);
|
||||
});
|
||||
|
||||
const body = await response.arrayBuffer();
|
||||
res.end(Buffer.from(body));
|
||||
}
|
||||
|
||||
function startNodeHttpServer(
|
||||
app: Hono,
|
||||
config: StatsServerConfig,
|
||||
): { close: () => void } {
|
||||
const server = http.createServer((req, res) => {
|
||||
void (async () => {
|
||||
try {
|
||||
await writeFetchResponse(res, await app.fetch(toFetchRequest(req)));
|
||||
} catch {
|
||||
res.statusCode = 500;
|
||||
res.end('Internal Server Error');
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
server.listen(config.port, '127.0.0.1');
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
server.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Load known words cache from disk into a Set. Returns null if unavailable. */
|
||||
function loadKnownWordsSet(cachePath: string | undefined): Set<string> | null {
|
||||
if (!cachePath || !existsSync(cachePath)) return null;
|
||||
@@ -1017,25 +1083,19 @@ export function startStatsServer(config: StatsServerConfig): { close: () => void
|
||||
};
|
||||
};
|
||||
|
||||
const server = bunRuntime.Bun?.serve
|
||||
? bunRuntime.Bun.serve({
|
||||
fetch: app.fetch,
|
||||
port: config.port,
|
||||
hostname: '127.0.0.1',
|
||||
})
|
||||
: serve({
|
||||
fetch: app.fetch,
|
||||
port: config.port,
|
||||
hostname: '127.0.0.1',
|
||||
});
|
||||
if (bunRuntime.Bun?.serve) {
|
||||
const server = bunRuntime.Bun.serve({
|
||||
fetch: app.fetch,
|
||||
port: config.port,
|
||||
hostname: '127.0.0.1',
|
||||
});
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
if ('stop' in server) {
|
||||
return {
|
||||
close: () => {
|
||||
server.stop();
|
||||
} else {
|
||||
server.close();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return startNodeHttpServer(app, config);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ function createSetupWindowHandler<TWindow>(
|
||||
title: config.title,
|
||||
show: true,
|
||||
autoHideMenuBar: true,
|
||||
resizable: config.resizable,
|
||||
minimizable: config.minimizable,
|
||||
maximizable: config.maximizable,
|
||||
...(config.resizable === undefined ? {} : { resizable: config.resizable }),
|
||||
...(config.minimizable === undefined ? {} : { minimizable: config.minimizable }),
|
||||
...(config.maximizable === undefined ? {} : { maximizable: config.maximizable }),
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
|
||||
Reference in New Issue
Block a user