diff --git a/package-lock.json b/package-lock.json index 1fae3048..de2b3702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "archiver": "^6.0.1", "compressible": "2.0.18", "compression": "1.7.4", + "cross-port-killer": "^1.4.0", "date-fns": "^3.3.1", "electron-squirrel-startup": "^1.0.0", "express": "4.19.2", @@ -8631,6 +8632,14 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true }, + "node_modules/cross-port-killer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cross-port-killer/-/cross-port-killer-1.4.0.tgz", + "integrity": "sha512-ujqfftKsSeorFMVI6JP25xMBixHEaDWVK+NarRZAGnJjR5AhebRQU+g+k/Lj8OHwM6f+wrrs8u5kkCdI7RLtxQ==", + "bin": { + "kill-port": "source/cli.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -27189,6 +27198,11 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true }, + "cross-port-killer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cross-port-killer/-/cross-port-killer-1.4.0.tgz", + "integrity": "sha512-ujqfftKsSeorFMVI6JP25xMBixHEaDWVK+NarRZAGnJjR5AhebRQU+g+k/Lj8OHwM6f+wrrs8u5kkCdI7RLtxQ==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index b2675174..8c5d4fba 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "archiver": "^6.0.1", "compressible": "2.0.18", "compression": "1.7.4", + "cross-port-killer": "^1.4.0", "date-fns": "^3.3.1", "electron-squirrel-startup": "^1.0.0", "express": "4.19.2", diff --git a/src/lib/port-finder.ts b/src/lib/port-finder.ts index bad501ad..245c3779 100644 --- a/src/lib/port-finder.ts +++ b/src/lib/port-finder.ts @@ -1,4 +1,5 @@ import http from 'http'; +import killPort from 'cross-port-killer'; const DEFAULT_PORT = 8881; @@ -72,6 +73,26 @@ class PortFinder { this.#unavailablePorts.push( port ); } } + + public releasePort( port?: number ): void { + if ( port && this.#unavailablePorts.includes( port ) ) { + killPort( port ) + .then( () => { + console.log( `Killed processes using port ${ port }` ); + } ) + .catch( ( err ) => { + console.error( `Failed to kill processes using port ${ port }: ${ err.message }` ); + } ) + .finally( () => { + // Ensure port finder cycles through newly reclaimed ports by removing + // from #unavailablePorts list and resetting #openPort. + this.#unavailablePorts = this.#unavailablePorts.filter( + ( unavailablePort ) => unavailablePort !== port + ); + this.#openPort = DEFAULT_PORT; + } ); + } + } } export const portFinder = PortFinder.getInstance(); diff --git a/src/site-server.ts b/src/site-server.ts index e05ae911..05f8e585 100644 --- a/src/site-server.ts +++ b/src/site-server.ts @@ -60,6 +60,7 @@ export class SiteServer { } await this.stop(); servers.delete( this.details.id ); + portFinder.releasePort( this.details.port ); } async start() {