fix(core): use next available port when the port for nx graph is in use (#31365)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> Command would fail silently with no error message ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> Rather than erroring, Nx will find the next available port and use that. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #30915
This commit is contained in:
parent
ab97087f2a
commit
2f37cb25a0
@ -83,7 +83,7 @@ describe('Nx Commands', () => {
|
|||||||
runCLI(`generate @nx/web:app apps/${app}`);
|
runCLI(`generate @nx/web:app apps/${app}`);
|
||||||
let url: string;
|
let url: string;
|
||||||
let port: number;
|
let port: number;
|
||||||
const child_process = await runCommandUntil(
|
const childProcess = await runCommandUntil(
|
||||||
`show project ${app} --web --open=false`,
|
`show project ${app} --web --open=false`,
|
||||||
(output) => {
|
(output) => {
|
||||||
console.log(output);
|
console.log(output);
|
||||||
@ -102,7 +102,68 @@ describe('Nx Commands', () => {
|
|||||||
// Check that url is alive
|
// Check that url is alive
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
await killProcessAndPorts(child_process.pid, port);
|
await killProcessAndPorts(childProcess.pid, port);
|
||||||
|
}, 700000);
|
||||||
|
|
||||||
|
it('should find alternative port when default port is occupied', async () => {
|
||||||
|
const app = uniq('myapp');
|
||||||
|
runCLI(`generate @nx/web:app apps/${app}`);
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
// Create a server that occupies the default port 4211
|
||||||
|
const blockingServer = http.createServer((req, res) => {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('blocking server');
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
blockingServer.listen(4211, '127.0.0.1', () => {
|
||||||
|
console.log('Blocking server started on port 4211');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let url: string;
|
||||||
|
let port: number;
|
||||||
|
let foundAlternativePort = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const childProcess = await runCommandUntil(
|
||||||
|
`show project ${app} --web --open=false`,
|
||||||
|
(output) => {
|
||||||
|
console.log(output);
|
||||||
|
// Should find alternative port and show message about port being in use
|
||||||
|
if (output.includes('Port 4211 was already in use, using port')) {
|
||||||
|
foundAlternativePort = true;
|
||||||
|
}
|
||||||
|
// output should contain 'Project graph started at http://127.0.0.1:{port}'
|
||||||
|
if (output.includes('Project graph started at http://')) {
|
||||||
|
const match = /https?:\/\/[\d.]+:(?<port>\d+)/.exec(output);
|
||||||
|
if (match) {
|
||||||
|
port = parseInt(match.groups.port);
|
||||||
|
url = match[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that an alternative port was found
|
||||||
|
expect(foundAlternativePort).toBe(true);
|
||||||
|
expect(port).not.toBe(4211);
|
||||||
|
expect(port).toBeGreaterThan(4211);
|
||||||
|
|
||||||
|
// Check that url is alive
|
||||||
|
const response = await fetch(url);
|
||||||
|
expect(response.status).toEqual(200);
|
||||||
|
|
||||||
|
await killProcessAndPorts(childProcess.pid, port);
|
||||||
|
} finally {
|
||||||
|
// Clean up the blocking server
|
||||||
|
blockingServer.close();
|
||||||
|
}
|
||||||
}, 700000);
|
}, 700000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import {
|
|||||||
parse,
|
parse,
|
||||||
relative,
|
relative,
|
||||||
} from 'path';
|
} from 'path';
|
||||||
|
import * as net from 'net';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { readNxJson, workspaceLayout } from '../../config/configuration';
|
import { readNxJson, workspaceLayout } from '../../config/configuration';
|
||||||
import {
|
import {
|
||||||
@ -490,17 +491,29 @@ export async function generateGraph(
|
|||||||
!!args.file && args.file.endsWith('html') ? 'build' : 'serve'
|
!!args.file && args.file.endsWith('html') ? 'build' : 'serve'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { app, url } = await startServer(
|
let app: Server;
|
||||||
html,
|
let url: URL;
|
||||||
environmentJs,
|
try {
|
||||||
args.host || '127.0.0.1',
|
const result = await startServer(
|
||||||
args.port || 4211,
|
html,
|
||||||
args.watch,
|
environmentJs,
|
||||||
affectedProjects,
|
args.host || '127.0.0.1',
|
||||||
args.focus,
|
args.port || 4211,
|
||||||
args.groupByFolder,
|
args.watch,
|
||||||
args.exclude
|
affectedProjects,
|
||||||
);
|
args.focus,
|
||||||
|
args.groupByFolder,
|
||||||
|
args.exclude
|
||||||
|
);
|
||||||
|
app = result.app;
|
||||||
|
url = result.url;
|
||||||
|
} catch (err) {
|
||||||
|
output.error({
|
||||||
|
title: 'Failed to start graph server',
|
||||||
|
bodyLines: [err.message],
|
||||||
|
});
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
url.pathname = args.view;
|
url.pathname = args.view;
|
||||||
|
|
||||||
@ -539,6 +552,33 @@ export async function generateGraph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findAvailablePort(
|
||||||
|
startPort: number,
|
||||||
|
host: string = '127.0.0.1'
|
||||||
|
): Promise<number> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const server = net.createServer();
|
||||||
|
|
||||||
|
server.listen(startPort, host, () => {
|
||||||
|
const port = (server.address() as net.AddressInfo).port;
|
||||||
|
server.close(() => {
|
||||||
|
resolve(port);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('error', (err: NodeJS.ErrnoException) => {
|
||||||
|
if (err.code === 'EADDRINUSE') {
|
||||||
|
// Port is in use, try the next one
|
||||||
|
findAvailablePort(startPort + 1, host)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function startServer(
|
async function startServer(
|
||||||
html: string,
|
html: string,
|
||||||
environmentJs: string,
|
environmentJs: string,
|
||||||
@ -676,9 +716,21 @@ async function startServer(
|
|||||||
process.on('SIGINT', () => handleTermination(128 + 2));
|
process.on('SIGINT', () => handleTermination(128 + 2));
|
||||||
process.on('SIGTERM', () => handleTermination(128 + 15));
|
process.on('SIGTERM', () => handleTermination(128 + 15));
|
||||||
|
|
||||||
return new Promise<{ app: Server; url: URL }>((res) => {
|
// Find an available port starting from the requested port
|
||||||
app.listen(port, host, () => {
|
const availablePort = await findAvailablePort(port, host);
|
||||||
res({ app, url: new URL(`http://${host}:${port}`) });
|
|
||||||
|
return new Promise<{ app: Server; url: URL }>((res, rej) => {
|
||||||
|
app.on('error', (err: NodeJS.ErrnoException) => {
|
||||||
|
rej(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(availablePort, host, () => {
|
||||||
|
if (availablePort !== port) {
|
||||||
|
output.note({
|
||||||
|
title: `Port ${port} was already in use, using port ${availablePort} instead`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res({ app, url: new URL(`http://${host}:${availablePort}`) });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user