projax 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LINKING.md +86 -0
  2. package/README.md +141 -0
  3. package/dist/core/database.d.ts +66 -0
  4. package/dist/core/database.js +312 -0
  5. package/dist/core/detector.d.ts +9 -0
  6. package/dist/core/detector.js +149 -0
  7. package/dist/core/index.d.ts +11 -0
  8. package/dist/core/index.js +43 -0
  9. package/dist/core/scanner.d.ts +8 -0
  10. package/dist/core/scanner.js +114 -0
  11. package/dist/electron/main.d.ts +1 -0
  12. package/dist/electron/main.js +310 -0
  13. package/dist/electron/port-extractor.d.ts +9 -0
  14. package/dist/electron/port-extractor.js +351 -0
  15. package/dist/electron/port-scanner.d.ts +13 -0
  16. package/dist/electron/port-scanner.js +93 -0
  17. package/dist/electron/port-utils.d.ts +21 -0
  18. package/dist/electron/port-utils.js +200 -0
  19. package/dist/electron/preload.d.ts +49 -0
  20. package/dist/electron/preload.js +21 -0
  21. package/dist/electron/renderer/assets/index-BZ6USRnW.js +42 -0
  22. package/dist/electron/renderer/assets/index-DNtxfrZe.js +42 -0
  23. package/dist/electron/renderer/assets/index-khk3K-qG.css +1 -0
  24. package/dist/electron/renderer/index.html +15 -0
  25. package/dist/electron/script-runner.d.ts +40 -0
  26. package/dist/electron/script-runner.js +651 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +971 -0
  29. package/dist/port-extractor.d.ts +9 -0
  30. package/dist/port-extractor.js +351 -0
  31. package/dist/port-scanner.d.ts +13 -0
  32. package/dist/port-scanner.js +93 -0
  33. package/dist/port-utils.d.ts +21 -0
  34. package/dist/port-utils.js +200 -0
  35. package/dist/script-runner.d.ts +40 -0
  36. package/dist/script-runner.js +651 -0
  37. package/package.json +40 -0
  38. package/rebuild-sqlite.js +82 -0
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractPortsFromProject = extractPortsFromProject;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Extract ports from a project's configuration files
41
+ */
42
+ async function extractPortsFromProject(projectPath) {
43
+ const ports = [];
44
+ // Extract from package.json scripts
45
+ const packageJsonPorts = extractPortsFromPackageJson(projectPath);
46
+ ports.push(...packageJsonPorts);
47
+ // Extract from vite.config.js/ts
48
+ const vitePorts = extractPortsFromViteConfig(projectPath);
49
+ ports.push(...vitePorts);
50
+ // Extract from next.config.js/ts
51
+ const nextPorts = extractPortsFromNextConfig(projectPath);
52
+ ports.push(...nextPorts);
53
+ // Extract from webpack.config.js
54
+ const webpackPorts = extractPortsFromWebpackConfig(projectPath);
55
+ ports.push(...webpackPorts);
56
+ // Extract from angular.json
57
+ const angularPorts = extractPortsFromAngularConfig(projectPath);
58
+ ports.push(...angularPorts);
59
+ // Extract from nuxt.config.js/ts
60
+ const nuxtPorts = extractPortsFromNuxtConfig(projectPath);
61
+ ports.push(...nuxtPorts);
62
+ // Extract from .env files
63
+ const envPorts = extractPortsFromEnvFiles(projectPath);
64
+ ports.push(...envPorts);
65
+ // Remove duplicates (same port and script)
66
+ const uniquePorts = Array.from(new Map(ports.map(p => [`${p.port}-${p.script || ''}`, p])).values());
67
+ return uniquePorts;
68
+ }
69
+ /**
70
+ * Extract ports from package.json scripts
71
+ */
72
+ function extractPortsFromPackageJson(projectPath) {
73
+ const ports = [];
74
+ const packageJsonPath = path.join(projectPath, 'package.json');
75
+ if (!fs.existsSync(packageJsonPath)) {
76
+ return ports;
77
+ }
78
+ try {
79
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
80
+ const scripts = packageJson.scripts || {};
81
+ for (const [scriptName, scriptCommand] of Object.entries(scripts)) {
82
+ if (typeof scriptCommand === 'string') {
83
+ // Look for --port, -p, PORT= patterns
84
+ const portPatterns = [
85
+ /--port\s+(\d+)/i,
86
+ /-p\s+(\d+)/i,
87
+ /PORT\s*=\s*(\d+)/i,
88
+ /VITE_PORT\s*=\s*(\d+)/i,
89
+ /NEXT_PORT\s*=\s*(\d+)/i,
90
+ /PORT=(\d+)/i,
91
+ ];
92
+ for (const pattern of portPatterns) {
93
+ const match = scriptCommand.match(pattern);
94
+ if (match && match[1]) {
95
+ const port = parseInt(match[1], 10);
96
+ if (!isNaN(port) && port > 0 && port <= 65535) {
97
+ ports.push({
98
+ port,
99
+ script: scriptName,
100
+ source: 'package.json',
101
+ });
102
+ break; // Only add once per script
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ catch (error) {
110
+ // Ignore parsing errors
111
+ }
112
+ return ports;
113
+ }
114
+ /**
115
+ * Extract ports from vite.config.js/ts
116
+ */
117
+ function extractPortsFromViteConfig(projectPath) {
118
+ const ports = [];
119
+ const configFiles = [
120
+ 'vite.config.js',
121
+ 'vite.config.ts',
122
+ 'vite.config.mjs',
123
+ 'vite.config.cjs',
124
+ ];
125
+ for (const configFile of configFiles) {
126
+ const configPath = path.join(projectPath, configFile);
127
+ if (!fs.existsSync(configPath))
128
+ continue;
129
+ try {
130
+ const content = fs.readFileSync(configPath, 'utf-8');
131
+ // Try to extract from server.port
132
+ const patterns = [
133
+ /server\s*:\s*\{[^}]*port\s*:\s*(\d+)/i,
134
+ /server\s*:\s*\{[^}]*port\s*:\s*['"](\d+)['"]/i,
135
+ /port\s*:\s*(\d+)/i,
136
+ /port\s*:\s*['"](\d+)['"]/i,
137
+ ];
138
+ for (const pattern of patterns) {
139
+ const match = content.match(pattern);
140
+ if (match && match[1]) {
141
+ const port = parseInt(match[1], 10);
142
+ if (!isNaN(port) && port > 0 && port <= 65535) {
143
+ ports.push({
144
+ port,
145
+ script: null,
146
+ source: configFile,
147
+ });
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ catch (error) {
154
+ // Ignore parsing errors
155
+ }
156
+ }
157
+ return ports;
158
+ }
159
+ /**
160
+ * Extract ports from next.config.js/ts
161
+ */
162
+ function extractPortsFromNextConfig(projectPath) {
163
+ const ports = [];
164
+ const configFiles = ['next.config.js', 'next.config.ts', 'next.config.mjs'];
165
+ for (const configFile of configFiles) {
166
+ const configPath = path.join(projectPath, configFile);
167
+ if (!fs.existsSync(configPath))
168
+ continue;
169
+ try {
170
+ const content = fs.readFileSync(configPath, 'utf-8');
171
+ // Next.js doesn't typically configure port in config, but check for devServer
172
+ const patterns = [
173
+ /devServer\s*:\s*\{[^}]*port\s*:\s*(\d+)/i,
174
+ /port\s*:\s*(\d+)/i,
175
+ ];
176
+ for (const pattern of patterns) {
177
+ const match = content.match(pattern);
178
+ if (match && match[1]) {
179
+ const port = parseInt(match[1], 10);
180
+ if (!isNaN(port) && port > 0 && port <= 65535) {
181
+ ports.push({
182
+ port,
183
+ script: null,
184
+ source: configFile,
185
+ });
186
+ break;
187
+ }
188
+ }
189
+ }
190
+ }
191
+ catch (error) {
192
+ // Ignore parsing errors
193
+ }
194
+ }
195
+ return ports;
196
+ }
197
+ /**
198
+ * Extract ports from webpack.config.js
199
+ */
200
+ function extractPortsFromWebpackConfig(projectPath) {
201
+ const ports = [];
202
+ const configFiles = ['webpack.config.js', 'webpack.config.ts'];
203
+ for (const configFile of configFiles) {
204
+ const configPath = path.join(projectPath, configFile);
205
+ if (!fs.existsSync(configPath))
206
+ continue;
207
+ try {
208
+ const content = fs.readFileSync(configPath, 'utf-8');
209
+ // Look for devServer.port
210
+ const patterns = [
211
+ /devServer\s*:\s*\{[^}]*port\s*:\s*(\d+)/i,
212
+ /devServer\s*:\s*\{[^}]*port\s*:\s*['"](\d+)['"]/i,
213
+ ];
214
+ for (const pattern of patterns) {
215
+ const match = content.match(pattern);
216
+ if (match && match[1]) {
217
+ const port = parseInt(match[1], 10);
218
+ if (!isNaN(port) && port > 0 && port <= 65535) {
219
+ ports.push({
220
+ port,
221
+ script: null,
222
+ source: configFile,
223
+ });
224
+ break;
225
+ }
226
+ }
227
+ }
228
+ }
229
+ catch (error) {
230
+ // Ignore parsing errors
231
+ }
232
+ }
233
+ return ports;
234
+ }
235
+ /**
236
+ * Extract ports from angular.json
237
+ */
238
+ function extractPortsFromAngularConfig(projectPath) {
239
+ const ports = [];
240
+ const configPath = path.join(projectPath, 'angular.json');
241
+ if (!fs.existsSync(configPath)) {
242
+ return ports;
243
+ }
244
+ try {
245
+ const angularJson = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
246
+ // Navigate through projects -> architect -> serve -> options -> port
247
+ const projects = angularJson.projects || {};
248
+ for (const projectName of Object.keys(projects)) {
249
+ const project = projects[projectName];
250
+ const serve = project?.architect?.serve;
251
+ if (serve?.options?.port) {
252
+ const port = parseInt(serve.options.port, 10);
253
+ if (!isNaN(port) && port > 0 && port <= 65535) {
254
+ ports.push({
255
+ port,
256
+ script: null,
257
+ source: 'angular.json',
258
+ });
259
+ }
260
+ }
261
+ }
262
+ }
263
+ catch (error) {
264
+ // Ignore parsing errors
265
+ }
266
+ return ports;
267
+ }
268
+ /**
269
+ * Extract ports from nuxt.config.js/ts
270
+ */
271
+ function extractPortsFromNuxtConfig(projectPath) {
272
+ const ports = [];
273
+ const configFiles = ['nuxt.config.js', 'nuxt.config.ts'];
274
+ for (const configFile of configFiles) {
275
+ const configPath = path.join(projectPath, configFile);
276
+ if (!fs.existsSync(configPath))
277
+ continue;
278
+ try {
279
+ const content = fs.readFileSync(configPath, 'utf-8');
280
+ // Look for server.port
281
+ const patterns = [
282
+ /server\s*:\s*\{[^}]*port\s*:\s*(\d+)/i,
283
+ /server\s*:\s*\{[^}]*port\s*:\s*['"](\d+)['"]/i,
284
+ ];
285
+ for (const pattern of patterns) {
286
+ const match = content.match(pattern);
287
+ if (match && match[1]) {
288
+ const port = parseInt(match[1], 10);
289
+ if (!isNaN(port) && port > 0 && port <= 65535) {
290
+ ports.push({
291
+ port,
292
+ script: null,
293
+ source: configFile,
294
+ });
295
+ break;
296
+ }
297
+ }
298
+ }
299
+ }
300
+ catch (error) {
301
+ // Ignore parsing errors
302
+ }
303
+ }
304
+ return ports;
305
+ }
306
+ /**
307
+ * Extract ports from .env files
308
+ */
309
+ function extractPortsFromEnvFiles(projectPath) {
310
+ const ports = [];
311
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
312
+ for (const envFile of envFiles) {
313
+ const envPath = path.join(projectPath, envFile);
314
+ if (!fs.existsSync(envPath))
315
+ continue;
316
+ try {
317
+ const content = fs.readFileSync(envPath, 'utf-8');
318
+ const lines = content.split('\n');
319
+ for (const line of lines) {
320
+ // Skip comments
321
+ if (line.trim().startsWith('#'))
322
+ continue;
323
+ // Look for PORT=, VITE_PORT=, NEXT_PORT=, etc.
324
+ const patterns = [
325
+ /^PORT\s*=\s*(\d+)/i,
326
+ /^VITE_PORT\s*=\s*(\d+)/i,
327
+ /^NEXT_PORT\s*=\s*(\d+)/i,
328
+ /^REACT_APP_PORT\s*=\s*(\d+)/i,
329
+ ];
330
+ for (const pattern of patterns) {
331
+ const match = line.match(pattern);
332
+ if (match && match[1]) {
333
+ const port = parseInt(match[1], 10);
334
+ if (!isNaN(port) && port > 0 && port <= 65535) {
335
+ ports.push({
336
+ port,
337
+ script: null,
338
+ source: envFile,
339
+ });
340
+ break;
341
+ }
342
+ }
343
+ }
344
+ }
345
+ }
346
+ catch (error) {
347
+ // Ignore parsing errors
348
+ }
349
+ }
350
+ return ports;
351
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Scan and index ports for a specific project
3
+ */
4
+ export declare function scanProjectPorts(projectId: number): Promise<void>;
5
+ /**
6
+ * Scan ports for all projects
7
+ */
8
+ export declare function scanAllProjectPorts(): Promise<void>;
9
+ /**
10
+ * Check if ports need to be rescanned (stale check)
11
+ * Returns true if ports haven't been scanned in the last 24 hours
12
+ */
13
+ export declare function shouldRescanPorts(projectId: number): boolean;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.scanProjectPorts = scanProjectPorts;
37
+ exports.scanAllProjectPorts = scanAllProjectPorts;
38
+ exports.shouldRescanPorts = shouldRescanPorts;
39
+ const core_1 = require("./core");
40
+ const port_extractor_1 = require("./port-extractor");
41
+ const fs = __importStar(require("fs"));
42
+ /**
43
+ * Scan and index ports for a specific project
44
+ */
45
+ async function scanProjectPorts(projectId) {
46
+ const db = (0, core_1.getDatabaseManager)();
47
+ const project = db.getProject(projectId);
48
+ if (!project) {
49
+ throw new Error(`Project with id ${projectId} not found`);
50
+ }
51
+ if (!fs.existsSync(project.path)) {
52
+ throw new Error(`Project path does not exist: ${project.path}`);
53
+ }
54
+ // Extract ports from project
55
+ const ports = await (0, port_extractor_1.extractPortsFromProject)(project.path);
56
+ // Remove existing ports for this project
57
+ db.removeProjectPorts(projectId);
58
+ // Add new ports
59
+ for (const portInfo of ports) {
60
+ db.addProjectPort(projectId, portInfo.port, portInfo.source, portInfo.script);
61
+ }
62
+ }
63
+ /**
64
+ * Scan ports for all projects
65
+ */
66
+ async function scanAllProjectPorts() {
67
+ const db = (0, core_1.getDatabaseManager)();
68
+ const projects = db.getAllProjects();
69
+ for (const project of projects) {
70
+ try {
71
+ await scanProjectPorts(project.id);
72
+ }
73
+ catch (error) {
74
+ // Continue with other projects if one fails
75
+ console.error(`Error scanning ports for project ${project.name}:`, error instanceof Error ? error.message : error);
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Check if ports need to be rescanned (stale check)
81
+ * Returns true if ports haven't been scanned in the last 24 hours
82
+ */
83
+ function shouldRescanPorts(projectId) {
84
+ const db = (0, core_1.getDatabaseManager)();
85
+ const ports = db.getProjectPorts(projectId);
86
+ if (ports.length === 0) {
87
+ return true; // No ports found, should scan
88
+ }
89
+ // Check if any port was detected more than 24 hours ago
90
+ const twentyFourHoursAgo = Math.floor(Date.now() / 1000) - (24 * 60 * 60);
91
+ const needsRescan = ports.some(port => (port.last_detected || 0) < twentyFourHoursAgo);
92
+ return needsRescan;
93
+ }
@@ -0,0 +1,21 @@
1
+ export interface ProcessInfo {
2
+ pid: number;
3
+ command: string;
4
+ }
5
+ /**
6
+ * Check if a port is in use (cross-platform)
7
+ */
8
+ export declare function detectPortInUse(port: number): Promise<boolean>;
9
+ /**
10
+ * Get process information for a port (cross-platform)
11
+ */
12
+ export declare function getProcessOnPort(port: number): Promise<ProcessInfo | null>;
13
+ /**
14
+ * Kill process(es) using a port (cross-platform)
15
+ */
16
+ export declare function killProcessOnPort(port: number): Promise<boolean>;
17
+ /**
18
+ * Extract port number from error messages
19
+ * Handles common port conflict error patterns
20
+ */
21
+ export declare function extractPortFromError(error: string): number | null;
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectPortInUse = detectPortInUse;
4
+ exports.getProcessOnPort = getProcessOnPort;
5
+ exports.killProcessOnPort = killProcessOnPort;
6
+ exports.extractPortFromError = extractPortFromError;
7
+ const child_process_1 = require("child_process");
8
+ const util_1 = require("util");
9
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
10
+ /**
11
+ * Check if a port is in use (cross-platform)
12
+ */
13
+ async function detectPortInUse(port) {
14
+ try {
15
+ if (process.platform === 'win32') {
16
+ // Windows: Use netstat
17
+ const { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
18
+ return stdout.trim().length > 0;
19
+ }
20
+ else {
21
+ // macOS/Linux: Use lsof
22
+ try {
23
+ const { stdout } = await execAsync(`lsof -ti:${port}`);
24
+ return stdout.trim().length > 0;
25
+ }
26
+ catch (error) {
27
+ // lsof returns non-zero exit code when port is not in use
28
+ if (error.code === 1) {
29
+ return false;
30
+ }
31
+ // Try netstat as fallback
32
+ try {
33
+ const { stdout } = await execAsync(`netstat -an | grep :${port}`);
34
+ return stdout.trim().length > 0;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ }
41
+ }
42
+ catch (error) {
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Get process information for a port (cross-platform)
48
+ */
49
+ async function getProcessOnPort(port) {
50
+ try {
51
+ if (process.platform === 'win32') {
52
+ // Windows: Get PID from netstat, then get process name
53
+ const { stdout: netstatOutput } = await execAsync(`netstat -ano | findstr :${port}`);
54
+ const lines = netstatOutput.trim().split('\n');
55
+ if (lines.length === 0)
56
+ return null;
57
+ // Parse PID from netstat output (last column)
58
+ const pidMatch = lines[0].trim().split(/\s+/).pop();
59
+ if (!pidMatch)
60
+ return null;
61
+ const pid = parseInt(pidMatch, 10);
62
+ if (isNaN(pid))
63
+ return null;
64
+ // Get process name
65
+ try {
66
+ const { stdout: tasklistOutput } = await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
67
+ const parts = tasklistOutput.trim().split(',');
68
+ const command = parts[0]?.replace(/"/g, '') || 'unknown';
69
+ return { pid, command };
70
+ }
71
+ catch {
72
+ return { pid, command: 'unknown' };
73
+ }
74
+ }
75
+ else {
76
+ // macOS/Linux: Use lsof
77
+ try {
78
+ const { stdout } = await execAsync(`lsof -ti:${port} -sTCP:LISTEN`);
79
+ const pids = stdout.trim().split('\n').filter(Boolean);
80
+ if (pids.length === 0)
81
+ return null;
82
+ const pid = parseInt(pids[0], 10);
83
+ if (isNaN(pid))
84
+ return null;
85
+ // Get process command
86
+ try {
87
+ const { stdout: psOutput } = await execAsync(`ps -p ${pid} -o comm=`);
88
+ const command = psOutput.trim() || 'unknown';
89
+ return { pid, command };
90
+ }
91
+ catch {
92
+ return { pid, command: 'unknown' };
93
+ }
94
+ }
95
+ catch (error) {
96
+ if (error.code === 1) {
97
+ return null;
98
+ }
99
+ throw error;
100
+ }
101
+ }
102
+ }
103
+ catch (error) {
104
+ return null;
105
+ }
106
+ }
107
+ /**
108
+ * Kill process(es) using a port (cross-platform)
109
+ */
110
+ async function killProcessOnPort(port) {
111
+ try {
112
+ if (process.platform === 'win32') {
113
+ // Windows: Get PID and kill with taskkill
114
+ const { stdout: netstatOutput } = await execAsync(`netstat -ano | findstr :${port}`);
115
+ const lines = netstatOutput.trim().split('\n');
116
+ if (lines.length === 0)
117
+ return false;
118
+ const pids = new Set();
119
+ for (const line of lines) {
120
+ const parts = line.trim().split(/\s+/);
121
+ const pid = parseInt(parts[parts.length - 1], 10);
122
+ if (!isNaN(pid)) {
123
+ pids.add(pid);
124
+ }
125
+ }
126
+ let killed = false;
127
+ for (const pid of pids) {
128
+ try {
129
+ await execAsync(`taskkill /F /PID ${pid}`);
130
+ killed = true;
131
+ }
132
+ catch {
133
+ // Ignore errors for individual PIDs
134
+ }
135
+ }
136
+ return killed;
137
+ }
138
+ else {
139
+ // macOS/Linux: Use lsof to find PIDs and kill them
140
+ try {
141
+ const { stdout } = await execAsync(`lsof -ti:${port}`);
142
+ const pids = stdout.trim().split('\n').filter(Boolean);
143
+ if (pids.length === 0)
144
+ return false;
145
+ let killed = false;
146
+ for (const pidStr of pids) {
147
+ const pid = parseInt(pidStr, 10);
148
+ if (!isNaN(pid)) {
149
+ try {
150
+ await execAsync(`kill -9 ${pid}`);
151
+ killed = true;
152
+ }
153
+ catch {
154
+ // Ignore errors for individual PIDs
155
+ }
156
+ }
157
+ }
158
+ return killed;
159
+ }
160
+ catch (error) {
161
+ if (error.code === 1) {
162
+ return false; // Port not in use
163
+ }
164
+ throw error;
165
+ }
166
+ }
167
+ }
168
+ catch (error) {
169
+ return false;
170
+ }
171
+ }
172
+ /**
173
+ * Extract port number from error messages
174
+ * Handles common port conflict error patterns
175
+ */
176
+ function extractPortFromError(error) {
177
+ // Common patterns:
178
+ // - "EADDRINUSE: address already in use :::3000"
179
+ // - "Port 3000 is already in use"
180
+ // - "Error: listen EADDRINUSE: address already in use 0.0.0.0:3000"
181
+ // - "Address already in use: 3000"
182
+ // - "port 3000 is already in use by another process"
183
+ const patterns = [
184
+ /(?:port|Port)\s+(\d+)\s+(?:is\s+)?(?:already\s+)?(?:in\s+use|taken)/i,
185
+ /EADDRINUSE[^:]*:\s*(?:address\s+already\s+in\s+use[^:]*:)?\s*(?:::|0\.0\.0\.0|127\.0\.0\.1|localhost)?:?(\d+)/i,
186
+ /(?:address|Address)\s+already\s+in\s+use[^:]*:?\s*(\d+)/i,
187
+ /(?:listen|Listen)\s+EADDRINUSE[^:]*:\s*(?:address\s+already\s+in\s+use[^:]*:)?\s*(?:::|0\.0\.0\.0|127\.0\.0\.1|localhost)?:?(\d+)/i,
188
+ /:(\d+)\s+\(EADDRINUSE\)/i,
189
+ ];
190
+ for (const pattern of patterns) {
191
+ const match = error.match(pattern);
192
+ if (match && match[1]) {
193
+ const port = parseInt(match[1], 10);
194
+ if (!isNaN(port) && port > 0 && port <= 65535) {
195
+ return port;
196
+ }
197
+ }
198
+ }
199
+ return null;
200
+ }