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.
- package/LINKING.md +86 -0
- package/README.md +141 -0
- package/dist/core/database.d.ts +66 -0
- package/dist/core/database.js +312 -0
- package/dist/core/detector.d.ts +9 -0
- package/dist/core/detector.js +149 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.js +43 -0
- package/dist/core/scanner.d.ts +8 -0
- package/dist/core/scanner.js +114 -0
- package/dist/electron/main.d.ts +1 -0
- package/dist/electron/main.js +310 -0
- package/dist/electron/port-extractor.d.ts +9 -0
- package/dist/electron/port-extractor.js +351 -0
- package/dist/electron/port-scanner.d.ts +13 -0
- package/dist/electron/port-scanner.js +93 -0
- package/dist/electron/port-utils.d.ts +21 -0
- package/dist/electron/port-utils.js +200 -0
- package/dist/electron/preload.d.ts +49 -0
- package/dist/electron/preload.js +21 -0
- package/dist/electron/renderer/assets/index-BZ6USRnW.js +42 -0
- package/dist/electron/renderer/assets/index-DNtxfrZe.js +42 -0
- package/dist/electron/renderer/assets/index-khk3K-qG.css +1 -0
- package/dist/electron/renderer/index.html +15 -0
- package/dist/electron/script-runner.d.ts +40 -0
- package/dist/electron/script-runner.js +651 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +971 -0
- package/dist/port-extractor.d.ts +9 -0
- package/dist/port-extractor.js +351 -0
- package/dist/port-scanner.d.ts +13 -0
- package/dist/port-scanner.js +93 -0
- package/dist/port-utils.d.ts +21 -0
- package/dist/port-utils.js +200 -0
- package/dist/script-runner.d.ts +40 -0
- package/dist/script-runner.js +651 -0
- package/package.json +40 -0
- 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
|
+
}
|