Skip to content

Commit a2c49a5

Browse files
fix(e2e): add robust port cleanup to all test startup scripts
- Add killPort() function to all startup scripts using fuser (CI) with lsof fallback (local) - Add delays after port cleanup to ensure OS releases ports fully - Fix port conflicts causing EADDRINUSE errors in CI - Apply to federated-css, federated-css-react-ssr test scripts This ensures ports are properly freed before starting new servers, preventing the port conflict issues seen in CI environments. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 268f440 commit a2c49a5

File tree

3 files changed

+61
-8
lines changed

3 files changed

+61
-8
lines changed

federated-css-react-ssr/scripts/start-exposes.cjs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { spawn } = require('node:child_process');
1+
const { spawn, execSync } = require('node:child_process');
22
const path = require('node:path');
33
const waitOn = require('wait-on');
44

@@ -8,6 +8,23 @@ function run(cmd, args, opts = {}) {
88
return spawn(cmd, args, { stdio: 'inherit', cwd: root, shell: true, ...opts });
99
}
1010

11+
function killPort(port) {
12+
try {
13+
// Try fuser first (more reliable in CI)
14+
execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: 'ignore' });
15+
} catch (e) {
16+
// Fallback to lsof for macOS/local development
17+
try {
18+
const pids = execSync(`lsof -ti tcp:${port} 2>/dev/null || true`, { encoding: 'utf8' }).trim();
19+
if (pids) {
20+
execSync(`kill -9 ${pids} 2>/dev/null || true`, { stdio: 'ignore' });
21+
}
22+
} catch (e2) {
23+
// Ignore errors, port might not be in use
24+
}
25+
}
26+
}
27+
1128
async function exec(cmd, args, opts = {}) {
1229
return new Promise((resolve, reject) => {
1330
const p = run(cmd, args, opts);
@@ -27,6 +44,11 @@ async function main() {
2744
{ dir: 'expose-less', port: 3007 },
2845
];
2946

47+
// Clean up all ports first
48+
console.log('[exposes] cleaning up ports...');
49+
exposes.forEach(({ port }) => killPort(port));
50+
await new Promise(resolve => setTimeout(resolve, 1000));
51+
3052
const procs = [];
3153
for (const { dir, port } of exposes) {
3254
const cwd = path.join('expose-apps', dir);

federated-css-react-ssr/scripts/start-shells.cjs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { spawn } = require('node:child_process');
1+
const { spawn, execSync } = require('node:child_process');
22
const path = require('node:path');
33
const waitOn = require('wait-on');
44

@@ -8,6 +8,23 @@ function run(cmd, args, opts = {}) {
88
return spawn(cmd, args, { stdio: 'inherit', cwd: root, shell: true, ...opts });
99
}
1010

11+
function killPort(port) {
12+
try {
13+
// Try fuser first (more reliable in CI)
14+
execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: 'ignore' });
15+
} catch (e) {
16+
// Fallback to lsof for macOS/local development
17+
try {
18+
const pids = execSync(`lsof -ti tcp:${port} 2>/dev/null || true`, { encoding: 'utf8' }).trim();
19+
if (pids) {
20+
execSync(`kill -9 ${pids} 2>/dev/null || true`, { stdio: 'ignore' });
21+
}
22+
} catch (e2) {
23+
// Ignore errors, port might not be in use
24+
}
25+
}
26+
}
27+
1128
async function exec(cmd, args, opts = {}) {
1229
return new Promise((resolve, reject) => {
1330
const p = run(cmd, args, opts);
@@ -26,6 +43,11 @@ async function main() {
2643
{ dir: 'scss-tailwind-css', port: 4005 },
2744
];
2845

46+
// Clean up all ports first
47+
console.log('[shells] cleaning up ports...');
48+
shells.forEach(({ port }) => killPort(port));
49+
await new Promise(resolve => setTimeout(resolve, 1000));
50+
2951
const procs = [];
3052
for (const { dir, port } of shells) {
3153
const cwd = path.join('shell-apps', dir);

federated-css/scripts/start-all.cjs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ function run(cmd, args) {
1717

1818
function killPort(port) {
1919
try {
20-
// Kill any process on the port (macOS/Linux compatible)
21-
const pids = execSync(`lsof -ti tcp:${port} 2>/dev/null || true`, { encoding: 'utf8' }).trim();
22-
if (pids) {
23-
execSync(`kill -9 ${pids} 2>/dev/null || true`, { stdio: 'ignore' });
24-
}
20+
// Try fuser first (more reliable in CI)
21+
execSync(`fuser -k ${port}/tcp 2>/dev/null || true`, { stdio: 'ignore' });
2522
} catch (e) {
26-
// Ignore errors, port might not be in use
23+
// Fallback to lsof for macOS/local development
24+
try {
25+
const pids = execSync(`lsof -ti tcp:${port} 2>/dev/null || true`, { encoding: 'utf8' }).trim();
26+
if (pids) {
27+
execSync(`kill -9 ${pids} 2>/dev/null || true`, { stdio: 'ignore' });
28+
}
29+
} catch (e2) {
30+
// Ignore errors, port might not be in use
31+
}
2732
}
2833
}
2934

@@ -52,6 +57,9 @@ async function main() {
5257
// Kill all potentially conflicting ports first
5358
console.log('[federated-css] cleaning up ports...');
5459
[...reactConsumers.map(c => c.port), ...exposes, ...nextConsumers.map(c => c.port)].forEach(killPort);
60+
61+
// Give OS time to fully release ports
62+
await new Promise(resolve => setTimeout(resolve, 1000));
5563

5664
console.log('[federated-css] starting consumers-react (sequential servers)...');
5765
for (const { dir, port, serve } of reactConsumers) {
@@ -120,6 +128,7 @@ async function main() {
120128
for (const { dir, port } of nextConsumers) {
121129
// Extra cleanup for each Next.js port in case previous one didn't clean up properly
122130
killPort(port);
131+
await new Promise(resolve => setTimeout(resolve, 500)); // Small delay to ensure port is released
123132
const cwd = path.join('consumers-nextjs', dir);
124133
const p = run('pnpm', ['-C', cwd, 'run', 'start']);
125134
procs.push(p);

0 commit comments

Comments
 (0)