mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-28 19:47:25 +00:00
66 lines
2.5 KiB
JavaScript
66 lines
2.5 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
import net from 'node:net';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
|
|
export async function reservePort() {
|
|
const server = net.createServer();
|
|
await new Promise((resolve, reject) => {
|
|
server.once('error', reject);
|
|
server.listen(0, '127.0.0.1', resolve);
|
|
});
|
|
const address = server.address();
|
|
const port = typeof address === 'object' && address ? address.port : null;
|
|
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
|
|
if (!port) throw new Error('failed to reserve relay port');
|
|
return port;
|
|
}
|
|
|
|
export async function startRelay(port) {
|
|
const output = [];
|
|
const child = spawn(process.execPath, ['server/index.js'], {
|
|
cwd: repoRoot,
|
|
env: {
|
|
...process.env,
|
|
PORT: String(port),
|
|
SERVER_SALT: 'koalasync-e2e-relay-salt-with-more-than-thirty-two-chars'
|
|
},
|
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
});
|
|
child.stdout.on('data', chunk => output.push(String(chunk)));
|
|
child.stderr.on('data', chunk => output.push(String(chunk)));
|
|
const deadline = Date.now() + 15000;
|
|
while (Date.now() < deadline) {
|
|
if (child.exitCode !== null) {
|
|
throw new Error(`relay exited with ${child.exitCode}: ${output.join('')}`);
|
|
}
|
|
try {
|
|
const remainingMs = Math.max(1, deadline - Date.now());
|
|
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
signal: globalThis.AbortSignal.timeout(Math.min(1000, remainingMs))
|
|
});
|
|
if (response.ok) return { child, output };
|
|
} catch (_error) {
|
|
// Relay is still starting.
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
child.kill('SIGTERM');
|
|
throw new Error(`relay did not become healthy: ${output.join('')}`);
|
|
}
|
|
|
|
export async function stopRelay(relay) {
|
|
if (!relay || relay.child.exitCode !== null) return;
|
|
relay.child.kill('SIGTERM');
|
|
const stopped = await Promise.race([
|
|
new Promise(resolve => relay.child.once('exit', () => resolve(true))),
|
|
new Promise(resolve => setTimeout(() => resolve(false), 7000))
|
|
]);
|
|
if (stopped) return;
|
|
relay.child.kill('SIGKILL');
|
|
await new Promise(resolve => relay.child.once('exit', resolve));
|
|
throw new Error(`relay required SIGKILL: ${relay.output.join('')}`);
|
|
}
|