projax 3.3.20 → 3.3.22

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 (66) hide show
  1. package/coverage/base.css +224 -0
  2. package/coverage/block-navigation.js +87 -0
  3. package/coverage/core-bridge.ts.html +268 -0
  4. package/coverage/favicon.png +0 -0
  5. package/coverage/index.html +176 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/core-bridge.ts.html +268 -0
  9. package/coverage/lcov-report/favicon.png +0 -0
  10. package/coverage/lcov-report/index.html +176 -0
  11. package/coverage/lcov-report/port-extractor.ts.html +1174 -0
  12. package/coverage/lcov-report/port-scanner.ts.html +301 -0
  13. package/coverage/lcov-report/port-utils.ts.html +670 -0
  14. package/coverage/lcov-report/prettify.css +1 -0
  15. package/coverage/lcov-report/prettify.js +2 -0
  16. package/coverage/lcov-report/script-runner.ts.html +3055 -0
  17. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  18. package/coverage/lcov-report/sorter.js +210 -0
  19. package/coverage/lcov.info +1117 -0
  20. package/coverage/port-extractor.ts.html +1174 -0
  21. package/coverage/port-scanner.ts.html +301 -0
  22. package/coverage/port-utils.ts.html +670 -0
  23. package/coverage/prettify.css +1 -0
  24. package/coverage/prettify.js +2 -0
  25. package/coverage/script-runner.ts.html +3055 -0
  26. package/coverage/sort-arrow-sprite.png +0 -0
  27. package/coverage/sorter.js +210 -0
  28. package/dist/__tests__/port-scanner.test.d.ts +1 -0
  29. package/dist/__tests__/port-scanner.test.js +170 -0
  30. package/dist/__tests__/port-utils.test.d.ts +1 -0
  31. package/dist/__tests__/port-utils.test.js +127 -0
  32. package/dist/__tests__/script-runner.test.d.ts +1 -0
  33. package/dist/__tests__/script-runner.test.js +491 -0
  34. package/dist/api/__tests__/database.test.d.ts +2 -0
  35. package/dist/api/__tests__/database.test.d.ts.map +1 -0
  36. package/dist/api/__tests__/database.test.js +485 -0
  37. package/dist/api/__tests__/database.test.js.map +1 -0
  38. package/dist/api/__tests__/routes.test.d.ts +2 -0
  39. package/dist/api/__tests__/routes.test.d.ts.map +1 -0
  40. package/dist/api/__tests__/routes.test.js +473 -0
  41. package/dist/api/__tests__/routes.test.js.map +1 -0
  42. package/dist/api/__tests__/scanner.test.d.ts +2 -0
  43. package/dist/api/__tests__/scanner.test.d.ts.map +1 -0
  44. package/dist/api/__tests__/scanner.test.js +402 -0
  45. package/dist/api/__tests__/scanner.test.js.map +1 -0
  46. package/dist/api/database.d.ts +1 -0
  47. package/dist/api/database.d.ts.map +1 -1
  48. package/dist/api/database.js +4 -0
  49. package/dist/api/database.js.map +1 -1
  50. package/dist/api/package.json +9 -1
  51. package/dist/api/routes/projects.d.ts.map +1 -1
  52. package/dist/api/routes/projects.js +8 -1
  53. package/dist/api/routes/projects.js.map +1 -1
  54. package/dist/api/routes/settings.d.ts.map +1 -1
  55. package/dist/api/routes/settings.js +2 -1
  56. package/dist/api/routes/settings.js.map +1 -1
  57. package/dist/api/services/scanner.d.ts.map +1 -1
  58. package/dist/api/services/scanner.js +7 -12
  59. package/dist/api/services/scanner.js.map +1 -1
  60. package/dist/electron/port-scanner.js +2 -2
  61. package/dist/electron/renderer/assets/index-C1SRt6Jx.js +62 -0
  62. package/dist/electron/renderer/assets/index-D1jmaGv5.css +1 -0
  63. package/dist/electron/renderer/index.html +2 -2
  64. package/dist/port-scanner.js +2 -2
  65. package/jest.config.js +18 -0
  66. package/package.json +7 -1
Binary file
@@ -0,0 +1,210 @@
1
+ /* eslint-disable */
2
+ var addSorting = (function() {
3
+ 'use strict';
4
+ var cols,
5
+ currentSort = {
6
+ index: 0,
7
+ desc: false
8
+ };
9
+
10
+ // returns the summary table element
11
+ function getTable() {
12
+ return document.querySelector('.coverage-summary');
13
+ }
14
+ // returns the thead element of the summary table
15
+ function getTableHeader() {
16
+ return getTable().querySelector('thead tr');
17
+ }
18
+ // returns the tbody element of the summary table
19
+ function getTableBody() {
20
+ return getTable().querySelector('tbody');
21
+ }
22
+ // returns the th element for nth column
23
+ function getNthColumn(n) {
24
+ return getTableHeader().querySelectorAll('th')[n];
25
+ }
26
+
27
+ function onFilterInput() {
28
+ const searchValue = document.getElementById('fileSearch').value;
29
+ const rows = document.getElementsByTagName('tbody')[0].children;
30
+
31
+ // Try to create a RegExp from the searchValue. If it fails (invalid regex),
32
+ // it will be treated as a plain text search
33
+ let searchRegex;
34
+ try {
35
+ searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
36
+ } catch (error) {
37
+ searchRegex = null;
38
+ }
39
+
40
+ for (let i = 0; i < rows.length; i++) {
41
+ const row = rows[i];
42
+ let isMatch = false;
43
+
44
+ if (searchRegex) {
45
+ // If a valid regex was created, use it for matching
46
+ isMatch = searchRegex.test(row.textContent);
47
+ } else {
48
+ // Otherwise, fall back to the original plain text search
49
+ isMatch = row.textContent
50
+ .toLowerCase()
51
+ .includes(searchValue.toLowerCase());
52
+ }
53
+
54
+ row.style.display = isMatch ? '' : 'none';
55
+ }
56
+ }
57
+
58
+ // loads the search box
59
+ function addSearchBox() {
60
+ var template = document.getElementById('filterTemplate');
61
+ var templateClone = template.content.cloneNode(true);
62
+ templateClone.getElementById('fileSearch').oninput = onFilterInput;
63
+ template.parentElement.appendChild(templateClone);
64
+ }
65
+
66
+ // loads all columns
67
+ function loadColumns() {
68
+ var colNodes = getTableHeader().querySelectorAll('th'),
69
+ colNode,
70
+ cols = [],
71
+ col,
72
+ i;
73
+
74
+ for (i = 0; i < colNodes.length; i += 1) {
75
+ colNode = colNodes[i];
76
+ col = {
77
+ key: colNode.getAttribute('data-col'),
78
+ sortable: !colNode.getAttribute('data-nosort'),
79
+ type: colNode.getAttribute('data-type') || 'string'
80
+ };
81
+ cols.push(col);
82
+ if (col.sortable) {
83
+ col.defaultDescSort = col.type === 'number';
84
+ colNode.innerHTML =
85
+ colNode.innerHTML + '<span class="sorter"></span>';
86
+ }
87
+ }
88
+ return cols;
89
+ }
90
+ // attaches a data attribute to every tr element with an object
91
+ // of data values keyed by column name
92
+ function loadRowData(tableRow) {
93
+ var tableCols = tableRow.querySelectorAll('td'),
94
+ colNode,
95
+ col,
96
+ data = {},
97
+ i,
98
+ val;
99
+ for (i = 0; i < tableCols.length; i += 1) {
100
+ colNode = tableCols[i];
101
+ col = cols[i];
102
+ val = colNode.getAttribute('data-value');
103
+ if (col.type === 'number') {
104
+ val = Number(val);
105
+ }
106
+ data[col.key] = val;
107
+ }
108
+ return data;
109
+ }
110
+ // loads all row data
111
+ function loadData() {
112
+ var rows = getTableBody().querySelectorAll('tr'),
113
+ i;
114
+
115
+ for (i = 0; i < rows.length; i += 1) {
116
+ rows[i].data = loadRowData(rows[i]);
117
+ }
118
+ }
119
+ // sorts the table using the data for the ith column
120
+ function sortByIndex(index, desc) {
121
+ var key = cols[index].key,
122
+ sorter = function(a, b) {
123
+ a = a.data[key];
124
+ b = b.data[key];
125
+ return a < b ? -1 : a > b ? 1 : 0;
126
+ },
127
+ finalSorter = sorter,
128
+ tableBody = document.querySelector('.coverage-summary tbody'),
129
+ rowNodes = tableBody.querySelectorAll('tr'),
130
+ rows = [],
131
+ i;
132
+
133
+ if (desc) {
134
+ finalSorter = function(a, b) {
135
+ return -1 * sorter(a, b);
136
+ };
137
+ }
138
+
139
+ for (i = 0; i < rowNodes.length; i += 1) {
140
+ rows.push(rowNodes[i]);
141
+ tableBody.removeChild(rowNodes[i]);
142
+ }
143
+
144
+ rows.sort(finalSorter);
145
+
146
+ for (i = 0; i < rows.length; i += 1) {
147
+ tableBody.appendChild(rows[i]);
148
+ }
149
+ }
150
+ // removes sort indicators for current column being sorted
151
+ function removeSortIndicators() {
152
+ var col = getNthColumn(currentSort.index),
153
+ cls = col.className;
154
+
155
+ cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
156
+ col.className = cls;
157
+ }
158
+ // adds sort indicators for current column being sorted
159
+ function addSortIndicators() {
160
+ getNthColumn(currentSort.index).className += currentSort.desc
161
+ ? ' sorted-desc'
162
+ : ' sorted';
163
+ }
164
+ // adds event listeners for all sorter widgets
165
+ function enableUI() {
166
+ var i,
167
+ el,
168
+ ithSorter = function ithSorter(i) {
169
+ var col = cols[i];
170
+
171
+ return function() {
172
+ var desc = col.defaultDescSort;
173
+
174
+ if (currentSort.index === i) {
175
+ desc = !currentSort.desc;
176
+ }
177
+ sortByIndex(i, desc);
178
+ removeSortIndicators();
179
+ currentSort.index = i;
180
+ currentSort.desc = desc;
181
+ addSortIndicators();
182
+ };
183
+ };
184
+ for (i = 0; i < cols.length; i += 1) {
185
+ if (cols[i].sortable) {
186
+ // add the click event handler on the th so users
187
+ // dont have to click on those tiny arrows
188
+ el = getNthColumn(i).querySelector('.sorter').parentElement;
189
+ if (el.addEventListener) {
190
+ el.addEventListener('click', ithSorter(i));
191
+ } else {
192
+ el.attachEvent('onclick', ithSorter(i));
193
+ }
194
+ }
195
+ }
196
+ }
197
+ // adds sorting functionality to the UI
198
+ return function() {
199
+ if (!getTable()) {
200
+ return;
201
+ }
202
+ cols = loadColumns();
203
+ loadData();
204
+ addSearchBox();
205
+ addSortIndicators();
206
+ enableUI();
207
+ };
208
+ })();
209
+
210
+ window.addEventListener('load', addSorting);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,170 @@
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
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ const os = __importStar(require("os"));
39
+ const port_scanner_1 = require("../port-scanner");
40
+ // We need to mock the core-bridge module since it depends on the core package
41
+ jest.mock('../core-bridge', () => {
42
+ const mockDb = {
43
+ projects: [],
44
+ ports: [],
45
+ getProject: function (id) {
46
+ return this.projects.find((p) => p.id === id) || null;
47
+ },
48
+ getAllProjects: function () {
49
+ return [...this.projects];
50
+ },
51
+ getProjectPorts: function (projectId) {
52
+ return this.ports.filter((p) => p.project_id === projectId);
53
+ },
54
+ removeProjectPorts: function (projectId) {
55
+ this.ports = this.ports.filter((p) => p.project_id !== projectId);
56
+ },
57
+ addProjectPort: function (projectId, port, source, script) {
58
+ const newPort = {
59
+ id: this.ports.length + 1,
60
+ project_id: projectId,
61
+ port,
62
+ script_name: script,
63
+ config_source: source,
64
+ last_detected: Math.floor(Date.now() / 1000),
65
+ created_at: Math.floor(Date.now() / 1000),
66
+ };
67
+ this.ports.push(newPort);
68
+ return newPort;
69
+ },
70
+ // Helper for tests
71
+ _reset: function () {
72
+ this.projects = [];
73
+ this.ports = [];
74
+ },
75
+ _addProject: function (id, name, projectPath) {
76
+ this.projects.push({
77
+ id,
78
+ name,
79
+ path: projectPath,
80
+ created_at: Math.floor(Date.now() / 1000),
81
+ });
82
+ },
83
+ };
84
+ return {
85
+ getDatabaseManager: () => mockDb,
86
+ };
87
+ });
88
+ const core_bridge_1 = require("../core-bridge");
89
+ describe('Port Scanner', () => {
90
+ let testDir;
91
+ let mockDb;
92
+ beforeEach(() => {
93
+ testDir = path.join(os.tmpdir(), `projax-port-scanner-test-${Date.now()}`);
94
+ fs.mkdirSync(testDir, { recursive: true });
95
+ mockDb = (0, core_bridge_1.getDatabaseManager)();
96
+ mockDb._reset();
97
+ });
98
+ afterEach(() => {
99
+ if (fs.existsSync(testDir)) {
100
+ fs.rmSync(testDir, { recursive: true, force: true });
101
+ }
102
+ });
103
+ describe('shouldRescanPorts', () => {
104
+ it('should return true when no ports exist for project', () => {
105
+ mockDb._addProject(1, 'Test Project', testDir);
106
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
107
+ expect(result).toBe(true);
108
+ });
109
+ it('should return false when ports were recently detected', () => {
110
+ mockDb._addProject(1, 'Test Project', testDir);
111
+ // Add port with current timestamp
112
+ mockDb.addProjectPort(1, 3000, 'package.json', 'dev');
113
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
114
+ expect(result).toBe(false);
115
+ });
116
+ it('should return true when ports are older than 24 hours', () => {
117
+ mockDb._addProject(1, 'Test Project', testDir);
118
+ // Add port with old timestamp (25 hours ago)
119
+ const oldTimestamp = Math.floor(Date.now() / 1000) - (25 * 60 * 60);
120
+ const port = mockDb.addProjectPort(1, 3000, 'package.json', 'dev');
121
+ port.last_detected = oldTimestamp;
122
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
123
+ expect(result).toBe(true);
124
+ });
125
+ it('should return true if any port is stale (mixed timestamps)', () => {
126
+ mockDb._addProject(1, 'Test Project', testDir);
127
+ // Add recent port
128
+ mockDb.addProjectPort(1, 3000, 'package.json', 'dev');
129
+ // Add stale port (26 hours ago)
130
+ const oldTimestamp = Math.floor(Date.now() / 1000) - (26 * 60 * 60);
131
+ const stalePort = mockDb.addProjectPort(1, 8080, 'package.json', 'api');
132
+ stalePort.last_detected = oldTimestamp;
133
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
134
+ expect(result).toBe(true);
135
+ });
136
+ it('should return false when all ports are recent', () => {
137
+ mockDb._addProject(1, 'Test Project', testDir);
138
+ // Add multiple recent ports
139
+ mockDb.addProjectPort(1, 3000, 'package.json', 'dev');
140
+ mockDb.addProjectPort(1, 8080, 'package.json', 'api');
141
+ mockDb.addProjectPort(1, 5432, 'docker-compose.yml', null);
142
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
143
+ expect(result).toBe(false);
144
+ });
145
+ it('should handle project with no database entry', () => {
146
+ // Project doesn't exist in database
147
+ const result = (0, port_scanner_1.shouldRescanPorts)(999);
148
+ // Should return true since no ports found
149
+ expect(result).toBe(true);
150
+ });
151
+ it('should consider 23 hours as recent (not stale)', () => {
152
+ mockDb._addProject(1, 'Test Project', testDir);
153
+ // Add port detected 23 hours ago
154
+ const timestamp = Math.floor(Date.now() / 1000) - (23 * 60 * 60);
155
+ const port = mockDb.addProjectPort(1, 3000, 'package.json', 'dev');
156
+ port.last_detected = timestamp;
157
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
158
+ expect(result).toBe(false);
159
+ });
160
+ it('should consider exactly 24 hours as stale', () => {
161
+ mockDb._addProject(1, 'Test Project', testDir);
162
+ // Add port detected exactly 24 hours ago
163
+ const timestamp = Math.floor(Date.now() / 1000) - (24 * 60 * 60);
164
+ const port = mockDb.addProjectPort(1, 3000, 'package.json', 'dev');
165
+ port.last_detected = timestamp;
166
+ const result = (0, port_scanner_1.shouldRescanPorts)(1);
167
+ expect(result).toBe(true);
168
+ });
169
+ });
170
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const port_utils_1 = require("../port-utils");
4
+ describe('Port Utils', () => {
5
+ describe('extractPortFromError', () => {
6
+ it('should extract port from EADDRINUSE error with port only', () => {
7
+ const error = 'EADDRINUSE: address already in use :::3000';
8
+ const port = (0, port_utils_1.extractPortFromError)(error);
9
+ expect(port).toBe(3000);
10
+ });
11
+ it('should extract port from EADDRINUSE error with IP address', () => {
12
+ const error = 'Error: listen EADDRINUSE: address already in use 0.0.0.0:3000';
13
+ const port = (0, port_utils_1.extractPortFromError)(error);
14
+ expect(port).toBe(3000);
15
+ });
16
+ it('should extract port from simple "Port X is already in use" message', () => {
17
+ const error = 'Port 3000 is already in use';
18
+ const port = (0, port_utils_1.extractPortFromError)(error);
19
+ expect(port).toBe(3000);
20
+ });
21
+ it('should extract port from "port X is already in use by another process"', () => {
22
+ const error = 'port 8080 is already in use by another process';
23
+ const port = (0, port_utils_1.extractPortFromError)(error);
24
+ expect(port).toBe(8080);
25
+ });
26
+ it('should extract port from "Address already in use" message', () => {
27
+ const error = 'Address already in use: 5432';
28
+ const port = (0, port_utils_1.extractPortFromError)(error);
29
+ expect(port).toBe(5432);
30
+ });
31
+ it('should extract port from EADDRINUSE with localhost', () => {
32
+ const error = 'EADDRINUSE: address already in use localhost:3000';
33
+ const port = (0, port_utils_1.extractPortFromError)(error);
34
+ expect(port).toBe(3000);
35
+ });
36
+ it('should extract port from EADDRINUSE with 127.0.0.1', () => {
37
+ const error = 'listen EADDRINUSE: address already in use 127.0.0.1:4000';
38
+ const port = (0, port_utils_1.extractPortFromError)(error);
39
+ expect(port).toBe(4000);
40
+ });
41
+ it('should extract port from error with multiple lines', () => {
42
+ const error = `
43
+ Error starting server
44
+ EADDRINUSE: address already in use :::3000
45
+ at Server.setupListenHandle
46
+ `;
47
+ const port = (0, port_utils_1.extractPortFromError)(error);
48
+ expect(port).toBe(3000);
49
+ });
50
+ it('should extract port from "(EADDRINUSE)" format', () => {
51
+ const error = 'Failed to start: :3000 (EADDRINUSE)';
52
+ const port = (0, port_utils_1.extractPortFromError)(error);
53
+ expect(port).toBe(3000);
54
+ });
55
+ it('should return null for error without port', () => {
56
+ const error = 'Some other error occurred';
57
+ const port = (0, port_utils_1.extractPortFromError)(error);
58
+ expect(port).toBeNull();
59
+ });
60
+ it('should return null for invalid port number', () => {
61
+ const error = 'Port 999999 is already in use';
62
+ const port = (0, port_utils_1.extractPortFromError)(error);
63
+ expect(port).toBeNull();
64
+ });
65
+ it('should return null for port 0', () => {
66
+ const error = 'Port 0 is already in use';
67
+ const port = (0, port_utils_1.extractPortFromError)(error);
68
+ expect(port).toBeNull();
69
+ });
70
+ it('should extract first valid port from multiple ports', () => {
71
+ const error = 'Port 3000 is in use, also port 4000';
72
+ const port = (0, port_utils_1.extractPortFromError)(error);
73
+ expect(port).toBe(3000);
74
+ });
75
+ it('should handle case-insensitive matching', () => {
76
+ const error = 'PORT 3000 is already in use';
77
+ const port = (0, port_utils_1.extractPortFromError)(error);
78
+ expect(port).toBe(3000);
79
+ });
80
+ it('should extract port from Node.js-style error', () => {
81
+ const error = `
82
+ Error: listen EADDRINUSE: address already in use :::3000
83
+ at Server.setupListenHandle [as _listen2] (node:net:1313:16)
84
+ at listenInCluster (node:net:1361:12)
85
+ `;
86
+ const port = (0, port_utils_1.extractPortFromError)(error);
87
+ expect(port).toBe(3000);
88
+ });
89
+ it('should extract port from Vite error message', () => {
90
+ const error = 'Port 5173 is already in use';
91
+ const port = (0, port_utils_1.extractPortFromError)(error);
92
+ expect(port).toBe(5173);
93
+ });
94
+ it('should extract port from Next.js error message', () => {
95
+ const error = 'Error: Port 3000 is already in use';
96
+ const port = (0, port_utils_1.extractPortFromError)(error);
97
+ expect(port).toBe(3000);
98
+ });
99
+ it('should extract common development ports', () => {
100
+ const testCases = [
101
+ { error: 'Port 3000 is in use', expected: 3000 }, // React/Next.js
102
+ { error: 'Port 8080 is in use', expected: 8080 }, // Common API
103
+ { error: 'Port 5173 is in use', expected: 5173 }, // Vite
104
+ { error: 'Port 4200 is in use', expected: 4200 }, // Angular
105
+ { error: 'Port 8000 is in use', expected: 8000 }, // Python/Django
106
+ { error: 'Port 5000 is in use', expected: 5000 }, // Flask
107
+ ];
108
+ testCases.forEach(({ error, expected }) => {
109
+ expect((0, port_utils_1.extractPortFromError)(error)).toBe(expected);
110
+ });
111
+ });
112
+ it('should handle errors with extra whitespace', () => {
113
+ const error = ' Port 3000 is already in use ';
114
+ const port = (0, port_utils_1.extractPortFromError)(error);
115
+ expect(port).toBe(3000);
116
+ });
117
+ it('should extract port from complex error messages', () => {
118
+ const error = `
119
+ Failed to start development server.
120
+ Error: listen EADDRINUSE: address already in use 0.0.0.0:3000
121
+ Please check if another process is already using port 3000.
122
+ `;
123
+ const port = (0, port_utils_1.extractPortFromError)(error);
124
+ expect(port).toBe(3000);
125
+ });
126
+ });
127
+ });
@@ -0,0 +1 @@
1
+ export {};