diff --git a/package.json b/package.json index cc31166e..48c9f541 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,11 @@ "build:desktop-only": "pnpm -F @prompt-optimizer/desktop build", "build:desktop": "npm-run-all build:core build:ui build:web build:desktop-only", "dev": "npm-run-all clean:dist build:core build:ui dev:parallel", - "dev:fresh": "npm-run-all clean pnpm-install dev", + "dev:fresh": "npm-run-all kill:dev clean pnpm-install dev", "dev:parallel": "concurrently -k -p \"[{name}]\" -n \"UI,WEB\" \"pnpm -F @prompt-optimizer/ui build --watch\" \"pnpm -F @prompt-optimizer/web dev\"", "dev:ext": "pnpm -F @prompt-optimizer/extension dev", "dev:desktop": "npm-run-all clean:dist build:core build:ui dev:desktop:parallel", - "dev:desktop:fresh": "npm-run-all clean pnpm-install dev:desktop", + "dev:desktop:fresh": "npm-run-all kill:dev clean pnpm-install dev:desktop", "dev:desktop:parallel": "concurrently -k -p \"[{name}]\" -n \"WEB,DESKTOP\" \"pnpm -F @prompt-optimizer/web dev\" \"pnpm -F @prompt-optimizer/desktop dev\"", "test": "pnpm -r test --run --passWithNoTests", "test:e2e": "playwright test", @@ -45,7 +45,8 @@ "lint:fix": "pnpm -F @prompt-optimizer/ui lint:fix", "bmad:refresh": "bmad-method install -f -i codex", "bmad:list": "bmad-method list:agents", - "bmad:validate": "bmad-method validate" + "bmad:validate": "bmad-method validate", + "kill:dev": "node scripts/kill-dev.js" }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^6.0.3", diff --git a/scripts/kill-dev.js b/scripts/kill-dev.js new file mode 100644 index 00000000..a23cc123 --- /dev/null +++ b/scripts/kill-dev.js @@ -0,0 +1,138 @@ +/** + * kill-dev v2 - Ultimate Safe Process Cleanup Script + * + * Strategy: + * 1. Clean up processes occupying ports (most critical) + * 2. Wait 2 seconds for parent processes to exit naturally + * 3. Check ports again to ensure complete cleanup + * + * About "missed processes": + * - Parent processes (pnpm) usually exit automatically after child processes terminate + * - vite build --watch doesn't occupy ports, exits with parent process + * - Even if missed, they don't occupy ports and won't block new starts + */ + +const { execSync } = require('child_process'); +const os = require('os'); + +console.log('๐Ÿงน Safely cleaning up project development processes...\n'); + +// Limit to project-specific ports so we do not kill other apps using defaults like 5173 +const PORTS = [18181]; + +/** + * Cross-platform non-blocking sleep + */ +function wait(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function killByPorts(round = 1) { + const platform = os.platform(); + let killedCount = 0; + const killedPids = new Set(); + + console.log(`๐Ÿ” Round ${round}: Checking port occupation...`); + + if (platform === 'win32') { + for (const port of PORTS) { + try { + const output = execSync(`netstat -ano | findstr ":${port}"`, { + encoding: 'utf-8', + stdio: 'pipe' + }); + + // Windows uses \r\n, so split by \n and trim each line + const lines = output.split('\n').map(line => line.trim()).filter(Boolean); + lines.forEach(line => { + const match = line.match(/LISTENING\s+(\d+)/); + if (match && !killedPids.has(match[1])) { + const pid = match[1]; + try { + execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' }); + console.log(` โœ“ Killed process on port ${port} (PID: ${pid})`); + killedPids.add(pid); + killedCount++; + } catch (e) { + // Process may have already exited + } + } + }); + } catch (error) { + // Port not occupied or command failed + } + } + } else { + for (const port of PORTS) { + try { + const output = execSync(`lsof -ti :${port}`, { + encoding: 'utf-8', + stdio: 'pipe' + }); + + const pids = output.split('\n').map(pid => pid.trim()).filter(Boolean); + pids.forEach(pid => { + if (!killedPids.has(pid)) { + try { + execSync(`kill -9 ${pid}`, { stdio: 'pipe' }); + console.log(` โœ“ Killed process on port ${port} (PID: ${pid})`); + killedPids.add(pid); + killedCount++; + } catch (e) { + // Process may have already exited + } + } + }); + } catch (error) { + // Port not occupied or command failed + } + } + } + + if (killedCount === 0) { + console.log(' โ„น๏ธ No processes found occupying ports'); + } + + return killedCount; +} + +async function main() { + try { + // First round cleanup + let totalKilled = killByPorts(1); + + if (totalKilled > 0) { + // Wait for parent processes to exit naturally (cross-platform) + console.log('\nโณ Waiting 2 seconds for parent processes to exit naturally...\n'); + await wait(2000); + + // Second round cleanup (ensure no missed processes) + const round2Killed = killByPorts(2); + totalKilled += round2Killed; + + if (round2Killed > 0) { + console.log('\n๐Ÿ’ก Found and cleaned up missed processes'); + } else { + console.log('\nโœ… Confirmed: All processes completely cleaned up'); + } + } + + console.log(`\n๐Ÿ“Š Total cleaned: ${totalKilled} process(es)`); + + if (totalKilled > 0) { + console.log('โœ… Cleanup complete! You can now run pnpm dev:fresh'); + } else { + console.log('โ„น๏ธ No processes found that need cleanup'); + } + + process.exit(0); + } catch (error) { + console.error('\nโŒ Cleanup failed:', error.message); + console.error('\n๐Ÿ’ก Please try:'); + console.error(' 1. Run this script again'); + console.error(' 2. Or manually terminate related processes in Task Manager'); + process.exit(1); + } +} + +main();