diff --git a/scripts/run-registry.ts b/scripts/run-registry.ts index 82d6513f40a2..a72a2a75ab14 100755 --- a/scripts/run-registry.ts +++ b/scripts/run-registry.ts @@ -15,6 +15,7 @@ import { parseConfigFile, runServer } from 'verdaccio'; import { maxConcurrentTasks } from './utils/concurrency'; import { PACKS_DIRECTORY } from './utils/constants'; +import { killPort } from './utils/kill-port'; import { getWorkspaces } from './utils/workspace'; program @@ -154,8 +155,17 @@ const publish = async (packages: { name: string; location: string }[], url: stri ); }; +const VERDACCIO_SERVER_PORT = 6001; +const VERDACCIO_REGISTRY_PORT = 6002; + +const cleanupVerdaccioProcesses = async () => { + await killPort([VERDACCIO_SERVER_PORT, VERDACCIO_REGISTRY_PORT]); +}; + const run = async () => { - const verdaccioUrl = `http://localhost:6001`; + await cleanupVerdaccioProcesses(); + + const verdaccioUrl = `http://localhost:${VERDACCIO_SERVER_PORT}`; logger.log(`📐 reading version of storybook`); logger.log(`🚛 listing storybook packages`); @@ -192,7 +202,7 @@ const run = async () => { '-e', 'test@test.com', '-r', - 'http://localhost:6002', + `http://localhost:${VERDACCIO_REGISTRY_PORT}`, ], { cwd: root, @@ -204,7 +214,7 @@ const run = async () => { ); if (opts.publish) { - await publish(packages, 'http://localhost:6002'); + await publish(packages, `http://localhost:${VERDACCIO_REGISTRY_PORT}`); } await rm(join(root, '.npmrc'), { force: true }); @@ -218,6 +228,8 @@ const run = async () => { run().catch((e) => { logger.error(e); rm(join(root, '.npmrc'), { force: true }).then(() => { - process.exit(1); + cleanupVerdaccioProcesses().then(() => { + process.exit(1); + }); }); }); diff --git a/scripts/utils/kill-port.ts b/scripts/utils/kill-port.ts new file mode 100644 index 000000000000..5e4ffc25d41a --- /dev/null +++ b/scripts/utils/kill-port.ts @@ -0,0 +1,89 @@ +// eslint-disable-next-line depend/ban-dependencies +import { execa } from 'execa'; + +/** + * This code is derived from the library `kill-port` by Tiaan du Plessis. Original repository: + * https://github.com/tiaanduplessis/kill-port + * + * The MIT License (MIT) + * + * Copyright (c) Tiaan du Plessis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +const killProcessInPort = async (port: number, method: 'tcp' | 'udp' = 'tcp') => { + if (!port || isNaN(port)) { + throw new Error('Invalid argument provided for port'); + } + let args: string[] = []; + let command: string; + + if (process.platform === 'win32') { + try { + const { stdout } = await execa('netstat', ['-nao']); + + if (!stdout) { + return; + } + + const lines = stdout.split('\n'); + 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+/gm); + if (match && match[0] && !acc.includes(match[0])) { + acc.push(match[0]); + } + return acc; + }, []); + + if (pids.length > 0) { + args = ['/F', ...pids.flatMap((pid) => ['/PID', pid])]; + command = 'TaskKill'; + } + } catch (error) { + throw new Error(`Failed to detect process on port ${port}: ${(error as Error).message}`); + } + } else { + const protocol = method === 'udp' ? 'udp' : 'tcp'; + args = [ + '-c', + `lsof -i ${protocol}:${port} | grep ${method === 'udp' ? 'UDP' : 'LISTEN'} | awk '{print $2}' | xargs kill -9`, + ]; + command = 'sh'; + } + + try { + if (command) { + await execa(command, args); + } else { + throw new Error('No command to kill process found'); + } + } catch (error: any) { + if (!error.message.includes('No such process')) { + console.error(`Failed to kill process on port ${port}`); + throw error; + } + } +}; + +export const killPort = async (ports: number | number[], method: 'tcp' | 'udp' = 'tcp') => { + const allPorts = Array.isArray(ports) ? ports : [ports]; + + console.log(`🚮 cleaning up process in ports: ${allPorts.join(', ')}`); + await Promise.all(allPorts.map((port) => killProcessInPort(port, method))); +};