Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion packages/ssh/src/server/server.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import { execFile } from 'child_process';
import { access } from 'fs/promises';
import { promisify } from 'util';

import { build } from 'tsdown';
Expand All @@ -26,6 +27,31 @@ import { serverTarget } from '../../tsdown.config';

const execFileAsync = promisify(execFile);

/**
* Wait for a file to be fully written and accessible.
* This helps prevent race conditions where the build completes
* but the file hasn't been fully flushed to disk yet.
*/
async function waitForFile(filePath: string): Promise<void> {
const maxAttempts = 10;
const delayMs = 100;

for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
await access(filePath);
// File exists, now try to verify it's readable by executing it with --help
// This ensures it's not just present but also syntactically valid
await execFileAsync('node', [filePath, '--help'], { timeout: 2000 });
return;
} catch {
if (attempt < maxAttempts - 1) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}
throw new Error(`File ${filePath} not ready after ${maxAttempts} attempts`);
}

/**
* Helper to run the server with args and capture output.
* The spawned process inherits the current process.env.
Expand Down Expand Up @@ -77,7 +103,10 @@ describe('MCP Server Executable - End-to-End Tests', () => {
// Build the project
await build({ ...serverTarget, logLevel: 'silent' });

// Use the built executable
// Wait for the file to be fully written and accessible
// This prevents race conditions in CI where the file might not be
// fully flushed to disk immediately after the build completes
await waitForFile('dist/server.js');
} catch (error) {
const execError = error as { stdout?: string; stderr?: string };
const errorMessage = [
Expand Down
Loading