From b78d353c7a1965d73f006a1c2361afb99f2cbe31 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 29 Apr 2022 13:49:53 +0100 Subject: [PATCH] fix(angular): cleanup ports on exit of mf server (#10060) --- .../module-federation-dev-server.impl.ts | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts index b697cc520cafa..5c34633feb665 100644 --- a/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -5,6 +5,51 @@ import { BuilderContext, createBuilder } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; import { join } from 'path'; import { webpackServer } from '../webpack-server/webpack-server.impl'; +import { exec, execSync } from 'child_process'; + +/** + * Inline kill-port to prevent adding a new dependency + */ +function killPort(port, method = 'tcp') { + port = Number.parseInt(port); + + if (!port) { + throw new Error('Invalid argument provided for port'); + } + + if (process.platform === 'win32') { + return exec('netstat -nao', (error, stdout, stderr) => { + if (!stdout) return; + + const lines = stdout.split('\n'); + // The second white-space delimited column of netstat output is the local port, + // which is the only port we care about. + // The regex here will match only the local port column of the output + const lineWithLocalPortRegEx = new RegExp( + `^ *${method.toUpperCase()} *[^ ]*:${port}`, + 'gm' + ); + const linesWithLocalPort = lines.filter((line) => + line.match(lineWithLocalPortRegEx) + ); + + const pids = linesWithLocalPort.reduce((acc, line) => { + const match = line.match(/(\d*)\w*(\n|$)/gm); + return match && match[0] && !acc.includes(match[0]) + ? acc.concat(match[0]) + : acc; + }, []); + + return execSync(`TaskKill /F /PID ${pids.join(' /PID ')}`); + }); + } + + return execSync( + `lsof -ni ${method === 'udp' ? 'udp' : 'tcp'}:${port} | grep ${ + method === 'udp' ? 'UDP' : 'LISTEN' + } | awk '{print $2}' | xargs kill -9` + ); +} export function moduleFederationDevServer( schema: Schema, @@ -40,13 +85,20 @@ export function moduleFederationDevServer( ? options.devRemotes : [options.devRemotes]; + const remotePorts: number[] = []; for (const remote of remotes) { const isDev = devServeRemotes.includes(remote); + const target = isDev ? 'serve' : 'serve-static'; + + remotePorts.push( + workspaceConfig.projects[remote]?.targets[target]?.options.port ?? 4200 + ); + scheduleTarget( context.workspaceRoot, { project: remote, - target: isDev ? 'serve' : 'serve-static', + target, configuration: context.target.configuration, runOptions: {}, executor: context.builder.builderName, @@ -55,6 +107,11 @@ export function moduleFederationDevServer( ); } + // Cleanup ports on kill + process.on('kill', () => { + remotePorts.forEach((port) => killPort(port)); + }); + return webpackServer(options, context); }