Switch to double quotes and reformat codebase with Biome

This commit is contained in:
Mohamed Boudra
2026-03-21 20:22:15 +07:00
parent ab40786882
commit f6579dbeb4
805 changed files with 38132 additions and 40175 deletions
+128 -105
View File
@@ -1,11 +1,11 @@
import { spawn, type ChildProcess, execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import net from 'node:net';
import { Buffer } from 'node:buffer';
import dotenv from 'dotenv';
import { spawn, type ChildProcess, execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import net from "node:net";
import { Buffer } from "node:buffer";
import dotenv from "dotenv";
type WaitForServerOptions = {
host?: string;
@@ -18,11 +18,11 @@ type WaitForServerOptions = {
async function getAvailablePort(): Promise<number> {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.once('error', reject);
server.once("error", reject);
server.listen(0, () => {
const address = server.address();
if (!address || typeof address === 'string') {
server.close(() => reject(new Error('Failed to acquire port')));
if (!address || typeof address === "string") {
server.close(() => reject(new Error("Failed to acquire port")));
return;
}
server.close(() => resolve(address.port));
@@ -40,18 +40,18 @@ function createLineBuffer(maxLines = 120): { add: (line: string) => void; dump:
}
},
dump() {
return lines.join('\n');
return lines.join("\n");
},
};
}
function formatRecentOutput(getRecentOutput?: () => string): string {
if (!getRecentOutput) {
return '';
return "";
}
const output = getRecentOutput().trim();
if (!output) {
return '';
return "";
}
return `\nRecent output:\n${output}`;
}
@@ -61,21 +61,15 @@ function sleep(ms: number): Promise<void> {
}
async function waitForServer(port: number, options: WaitForServerOptions): Promise<void> {
const {
host = '127.0.0.1',
timeoutMs = 15000,
label,
childProcess,
getRecentOutput,
} = options;
const { host = "127.0.0.1", timeoutMs = 15000, label, childProcess, getRecentOutput } = options;
const start = Date.now();
let lastConnectionError: unknown = null;
while (Date.now() - start < timeoutMs) {
if (childProcess && childProcess.exitCode !== null) {
const signal = childProcess.signalCode ? `, signal ${childProcess.signalCode}` : '';
const signal = childProcess.signalCode ? `, signal ${childProcess.signalCode}` : "";
throw new Error(
`${label} exited before listening on ${host}:${port} (exit code ${childProcess.exitCode}${signal}).${formatRecentOutput(getRecentOutput)}`
`${label} exited before listening on ${host}:${port} (exit code ${childProcess.exitCode}${signal}).${formatRecentOutput(getRecentOutput)}`,
);
}
@@ -89,7 +83,7 @@ async function waitForServer(port: number, options: WaitForServerOptions): Promi
socket.destroy();
reject(new Error(`Connection timed out to ${host}:${port}`));
});
socket.on('error', reject);
socket.on("error", reject);
});
return;
} catch (error) {
@@ -99,9 +93,11 @@ async function waitForServer(port: number, options: WaitForServerOptions): Promi
}
const reason =
lastConnectionError instanceof Error ? ` Last connection error: ${lastConnectionError.message}` : '';
lastConnectionError instanceof Error
? ` Last connection error: ${lastConnectionError.message}`
: "";
throw new Error(
`${label} did not start on ${host}:${port} within ${timeoutMs}ms.${reason}${formatRecentOutput(getRecentOutput)}`
`${label} did not start on ${host}:${port} within ${timeoutMs}ms.${reason}${formatRecentOutput(getRecentOutput)}`,
);
}
@@ -126,15 +122,15 @@ async function stopProcess(child: ChildProcess | null): Promise<void> {
if (child.exitCode !== null || child.signalCode !== null) {
return;
}
child.kill('SIGTERM');
child.kill("SIGTERM");
await new Promise<void>((resolve) => {
const timeout = setTimeout(() => {
if (child.exitCode === null && child.signalCode === null) {
child.kill('SIGKILL');
child.kill("SIGKILL");
}
resolve();
}, 5000);
child.once('exit', () => {
child.once("exit", () => {
clearTimeout(timeout);
resolve();
});
@@ -144,7 +140,7 @@ async function stopProcess(child: ChildProcess | null): Promise<void> {
function summarizeOpenAiErrorBody(body: string): string {
const trimmed = body.trim();
if (!trimmed) {
return 'empty response body';
return "empty response body";
}
if (trimmed.length <= 240) {
return trimmed;
@@ -159,8 +155,8 @@ async function isOpenAiApiKeyUsable(apiKey: string | undefined): Promise<boolean
}
try {
const response = await fetch('https://api.openai.com/v1/models?limit=1', {
method: 'GET',
const response = await fetch("https://api.openai.com/v1/models?limit=1", {
method: "GET",
headers: {
Authorization: `Bearer ${key}`,
},
@@ -170,14 +166,14 @@ async function isOpenAiApiKeyUsable(apiKey: string | undefined): Promise<boolean
}
const body = await response.text();
console.warn(
`[e2e] OPENAI_API_KEY probe failed (${response.status}): ${summarizeOpenAiErrorBody(body)}`
`[e2e] OPENAI_API_KEY probe failed (${response.status}): ${summarizeOpenAiErrorBody(body)}`,
);
return false;
} catch (error) {
console.warn(
`[e2e] OPENAI_API_KEY probe request failed: ${
error instanceof Error ? error.message : String(error)
}`
}`,
);
return false;
}
@@ -196,42 +192,42 @@ type OfferPayload = {
};
function stripAnsi(input: string): string {
return input.replace(/\u001b\[[0-9;]*m/g, '');
return input.replace(/\u001b\[[0-9;]*m/g, "");
}
function ensureRelayBuildArtifact(repoRoot: string): void {
const relayDistEntry = path.join(repoRoot, 'packages/relay/dist/e2ee.js');
const relayDistEntry = path.join(repoRoot, "packages/relay/dist/e2ee.js");
if (existsSync(relayDistEntry)) {
return;
}
console.log('[e2e] Building @getpaseo/relay for daemon startup');
execSync('npm run build --workspace=@getpaseo/relay', {
console.log("[e2e] Building @getpaseo/relay for daemon startup");
execSync("npm run build --workspace=@getpaseo/relay", {
cwd: repoRoot,
stdio: 'inherit',
stdio: "inherit",
});
}
function decodeOfferFromFragmentUrl(url: string): OfferPayload {
const marker = '#offer=';
const marker = "#offer=";
const idx = url.indexOf(marker);
if (idx === -1) {
throw new Error(`missing ${marker} fragment: ${url}`);
}
const encoded = url.slice(idx + marker.length);
const json = Buffer.from(encoded, 'base64url').toString('utf8');
const json = Buffer.from(encoded, "base64url").toString("utf8");
const offer = JSON.parse(json) as Partial<OfferPayload>;
if (offer.v !== 2) throw new Error('offer.v missing/invalid');
if (!offer.serverId) throw new Error('offer.serverId missing');
if (!offer.daemonPublicKeyB64) throw new Error('offer.daemonPublicKeyB64 missing');
if (!offer.relay?.endpoint) throw new Error('offer.relay.endpoint missing');
if (offer.v !== 2) throw new Error("offer.v missing/invalid");
if (!offer.serverId) throw new Error("offer.serverId missing");
if (!offer.daemonPublicKeyB64) throw new Error("offer.daemonPublicKeyB64 missing");
if (!offer.relay?.endpoint) throw new Error("offer.relay.endpoint missing");
return offer as OfferPayload;
}
export default async function globalSetup() {
const repoRoot = path.resolve(__dirname, '../../..');
const repoRoot = path.resolve(__dirname, "../../..");
ensureRelayBuildArtifact(repoRoot);
const envTestPath = path.join(repoRoot, '.env.test');
const envTestPath = path.join(repoRoot, ".env.test");
if (existsSync(envTestPath)) {
dotenv.config({ path: envTestPath });
}
@@ -239,13 +235,17 @@ export default async function globalSetup() {
const port = await getAvailablePort();
let relayPort = 0;
const metroPort = await getAvailablePort();
paseoHome = await mkdtemp(path.join(tmpdir(), 'paseo-e2e-home-'));
paseoHome = await mkdtemp(path.join(tmpdir(), "paseo-e2e-home-"));
let relayLineBuffer = createLineBuffer();
const metroLineBuffer = createLineBuffer();
const daemonLineBuffer = createLineBuffer();
const cleanup = async () => {
await Promise.all([stopProcess(daemonProcess), stopProcess(metroProcess), stopProcess(relayProcess)]);
await Promise.all([
stopProcess(daemonProcess),
stopProcess(metroProcess),
stopProcess(relayProcess),
]);
daemonProcess = null;
metroProcess = null;
relayProcess = null;
@@ -256,24 +256,30 @@ export default async function globalSetup() {
};
const openAiUsable = await isOpenAiApiKeyUsable(process.env.OPENAI_API_KEY);
const defaultLocalModelsDir = path.join(process.env.HOME ?? '', '.paseo', 'models', 'local-speech');
const hasDefaultLocalModelsDir = defaultLocalModelsDir.trim().length > 0 && existsSync(defaultLocalModelsDir);
const dictationProvider = openAiUsable ? 'openai' : 'local';
const defaultLocalModelsDir = path.join(
process.env.HOME ?? "",
".paseo",
"models",
"local-speech",
);
const hasDefaultLocalModelsDir =
defaultLocalModelsDir.trim().length > 0 && existsSync(defaultLocalModelsDir);
const dictationProvider = openAiUsable ? "openai" : "local";
if (dictationProvider === 'local' && !hasDefaultLocalModelsDir) {
if (dictationProvider === "local" && !hasDefaultLocalModelsDir) {
throw new Error(
'OpenAI key is not usable and local speech models are unavailable at ~/.paseo/models/local-speech. ' +
'Either provide a valid OPENAI_API_KEY or install local speech models before running app e2e tests.'
"OpenAI key is not usable and local speech models are unavailable at ~/.paseo/models/local-speech. " +
"Either provide a valid OPENAI_API_KEY or install local speech models before running app e2e tests.",
);
}
const localModelsDir = dictationProvider === 'local' ? defaultLocalModelsDir : null;
const localModelsDir = dictationProvider === "local" ? defaultLocalModelsDir : null;
console.log(
`[e2e] Dictation STT provider: ${dictationProvider}${openAiUsable ? '' : ' (OpenAI probe failed)'}`
`[e2e] Dictation STT provider: ${dictationProvider}${openAiUsable ? "" : " (OpenAI probe failed)"}`,
);
try {
const relayDir = path.resolve(__dirname, '..', '..', 'relay');
const relayDir = path.resolve(__dirname, "..", "..", "relay");
const maxRelayStartupAttempts = 5;
let relayStarted = false;
let lastRelayStartupError: unknown = null;
@@ -285,18 +291,21 @@ export default async function globalSetup() {
let relayReadyForSelectedPort = false;
relayProcess = spawn(
'npx',
['wrangler', 'dev', '--local', '--ip', '127.0.0.1', '--port', String(relayPort)],
"npx",
["wrangler", "dev", "--local", "--ip", "127.0.0.1", "--port", String(relayPort)],
{
cwd: relayDir,
env: { ...process.env },
stdio: ['ignore', 'pipe', 'pipe'],
stdio: ["ignore", "pipe", "pipe"],
detached: false,
}
},
);
relayProcess.stdout?.on('data', (data: Buffer) => {
const lines = data.toString().split('\n').filter((line) => line.trim());
relayProcess.stdout?.on("data", (data: Buffer) => {
const lines = data
.toString()
.split("\n")
.filter((line) => line.trim());
for (const line of lines) {
relayLineBuffer.add(`[stdout] ${line}`);
const failure = parseRelayStartupFailure(line);
@@ -311,8 +320,11 @@ export default async function globalSetup() {
console.log(`[relay] ${line}`);
}
});
relayProcess.stderr?.on('data', (data: Buffer) => {
const lines = data.toString().split('\n').filter((line) => line.trim());
relayProcess.stderr?.on("data", (data: Buffer) => {
const lines = data
.toString()
.split("\n")
.filter((line) => line.trim());
for (const line of lines) {
relayLineBuffer.add(`[stderr] ${line}`);
const failure = parseRelayStartupFailure(line);
@@ -330,7 +342,7 @@ export default async function globalSetup() {
try {
await waitForServer(relayPort, {
label: 'Relay dev server',
label: "Relay dev server",
timeoutMs: 30000,
childProcess: relayProcess,
getRecentOutput: relayLineBuffer.dump,
@@ -353,15 +365,15 @@ export default async function globalSetup() {
if (!relayReadyForSelectedPort) {
throw new Error(
`Relay process did not report ready for selected port ${relayPort}.${formatRecentOutput(
relayLineBuffer.dump
)}`
relayLineBuffer.dump,
)}`,
);
}
if (relayProcess.exitCode !== null || relayProcess.signalCode !== null) {
throw new Error(
`Relay process exited before startup completed (exit code ${relayProcess.exitCode}, signal ${relayProcess.signalCode}).${formatRecentOutput(
relayLineBuffer.dump
)}`
relayLineBuffer.dump,
)}`,
);
}
@@ -380,40 +392,46 @@ export default async function globalSetup() {
? lastRelayStartupError.message
: String(lastRelayStartupError);
throw new Error(
`Failed to start relay dev server after ${maxRelayStartupAttempts} attempts. ${message}`
`Failed to start relay dev server after ${maxRelayStartupAttempts} attempts. ${message}`,
);
}
// Start Metro bundler on dynamic port
const appDir = path.resolve(__dirname, '..');
metroProcess = spawn('npx', ['expo', 'start', '--web', '--port', String(metroPort)], {
const appDir = path.resolve(__dirname, "..");
metroProcess = spawn("npx", ["expo", "start", "--web", "--port", String(metroPort)], {
cwd: appDir,
env: {
...process.env,
BROWSER: 'none', // Don't auto-open browser
BROWSER: "none", // Don't auto-open browser
},
stdio: ['ignore', 'pipe', 'pipe'],
stdio: ["ignore", "pipe", "pipe"],
detached: false,
});
metroProcess.stdout?.on('data', (data: Buffer) => {
const lines = data.toString().split('\n').filter((line) => line.trim());
metroProcess.stdout?.on("data", (data: Buffer) => {
const lines = data
.toString()
.split("\n")
.filter((line) => line.trim());
for (const line of lines) {
metroLineBuffer.add(`[stdout] ${line}`);
console.log(`[metro] ${line}`);
}
});
metroProcess.stderr?.on('data', (data: Buffer) => {
const lines = data.toString().split('\n').filter((line) => line.trim());
metroProcess.stderr?.on("data", (data: Buffer) => {
const lines = data
.toString()
.split("\n")
.filter((line) => line.trim());
for (const line of lines) {
metroLineBuffer.add(`[stderr] ${line}`);
console.error(`[metro] ${line}`);
}
});
const serverDir = path.resolve(__dirname, '../../..', 'packages/server');
const tsxBin = execSync('which tsx').toString().trim();
const serverDir = path.resolve(__dirname, "../../..", "packages/server");
const tsxBin = execSync("which tsx").toString().trim();
let offerPayload: OfferPayload | null = null;
let offerResolve: (() => void) | null = null;
@@ -421,33 +439,33 @@ export default async function globalSetup() {
offerResolve = resolve;
});
daemonProcess = spawn(tsxBin, ['src/server/index.ts'], {
daemonProcess = spawn(tsxBin, ["src/server/index.ts"], {
cwd: serverDir,
env: {
...process.env,
PASEO_HOME: paseoHome,
PASEO_SERVER_ID: 'srv_e2e_test_daemon',
PASEO_SERVER_ID: "srv_e2e_test_daemon",
PASEO_LISTEN: `0.0.0.0:${port}`,
PASEO_RELAY_ENDPOINT: `127.0.0.1:${relayPort}`,
PASEO_CORS_ORIGINS: `http://localhost:${metroPort}`,
// Use OpenAI speech providers in e2e to avoid local model bootstrapping delays.
PASEO_DICTATION_ENABLED: '1',
PASEO_VOICE_MODE_ENABLED: '1',
PASEO_DICTATION_ENABLED: "1",
PASEO_VOICE_MODE_ENABLED: "1",
PASEO_DICTATION_STT_PROVIDER: dictationProvider,
PASEO_VOICE_STT_PROVIDER: 'openai',
PASEO_VOICE_TTS_PROVIDER: 'openai',
PASEO_VOICE_STT_PROVIDER: "openai",
PASEO_VOICE_TTS_PROVIDER: "openai",
...(localModelsDir ? { PASEO_LOCAL_MODELS_DIR: localModelsDir } : {}),
NODE_ENV: 'development',
NODE_ENV: "development",
},
stdio: ['ignore', 'pipe', 'pipe'],
stdio: ["ignore", "pipe", "pipe"],
detached: false,
});
let stdoutBuffer = '';
daemonProcess.stdout?.on('data', (data: Buffer) => {
stdoutBuffer += data.toString('utf8');
const lines = stdoutBuffer.split('\n');
stdoutBuffer = lines.pop() ?? '';
let stdoutBuffer = "";
daemonProcess.stdout?.on("data", (data: Buffer) => {
stdoutBuffer += data.toString("utf8");
const lines = stdoutBuffer.split("\n");
stdoutBuffer = lines.pop() ?? "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
@@ -456,13 +474,13 @@ export default async function globalSetup() {
const clean = stripAnsi(trimmed);
try {
const obj = JSON.parse(clean) as { msg?: string; url?: string };
if (obj.msg === 'pairing_offer' && typeof obj.url === 'string') {
if (obj.msg === "pairing_offer" && typeof obj.url === "string") {
offerPayload = decodeOfferFromFragmentUrl(obj.url);
offerResolve?.();
}
} catch {
const match = clean.match(/https?:\/\/[^\s"]+#offer=[A-Za-z0-9_-]+/);
if (match && clean.includes('pairing_offer')) {
if (match && clean.includes("pairing_offer")) {
try {
offerPayload = decodeOfferFromFragmentUrl(match[0]);
offerResolve?.();
@@ -476,8 +494,11 @@ export default async function globalSetup() {
}
});
daemonProcess.stderr?.on('data', (data: Buffer) => {
const lines = data.toString().split('\n').filter((line) => line.trim());
daemonProcess.stderr?.on("data", (data: Buffer) => {
const lines = data
.toString()
.split("\n")
.filter((line) => line.trim());
for (const line of lines) {
daemonLineBuffer.add(`[stderr] ${line}`);
console.error(`[daemon] ${line}`);
@@ -487,12 +508,12 @@ export default async function globalSetup() {
// Wait for both daemon and Metro to be ready
await Promise.all([
waitForServer(port, {
label: 'Paseo daemon',
label: "Paseo daemon",
childProcess: daemonProcess,
getRecentOutput: daemonLineBuffer.dump,
}),
waitForServer(metroPort, {
label: 'Metro web server',
label: "Metro web server",
timeoutMs: 120000, // Metro can take longer to start
childProcess: metroProcess,
getRecentOutput: metroLineBuffer.dump,
@@ -503,11 +524,11 @@ export default async function globalSetup() {
await Promise.race([
offerPromise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timed out waiting for pairing_offer log')), 15000)
setTimeout(() => reject(new Error("Timed out waiting for pairing_offer log")), 15000),
),
]);
if (!offerPayload) {
throw new Error('pairing_offer was not parsed from daemon logs');
throw new Error("pairing_offer was not parsed from daemon logs");
}
const offer = offerPayload as OfferPayload;
@@ -516,11 +537,13 @@ export default async function globalSetup() {
process.env.E2E_SERVER_ID = offer.serverId;
process.env.E2E_RELAY_DAEMON_PUBLIC_KEY = offer.daemonPublicKeyB64;
process.env.E2E_METRO_PORT = String(metroPort);
console.log(`[e2e] Test daemon started on port ${port}, Metro on port ${metroPort}, home: ${paseoHome}`);
console.log(
`[e2e] Test daemon started on port ${port}, Metro on port ${metroPort}, home: ${paseoHome}`,
);
return async () => {
await cleanup();
console.log('[e2e] Test daemon stopped');
console.log("[e2e] Test daemon stopped");
};
} catch (error) {
await cleanup();