projax 3.3.25 ā 3.3.27
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/dist/api/database.d.ts +6 -1
- package/dist/api/database.d.ts.map +1 -1
- package/dist/api/database.js +49 -0
- package/dist/api/database.js.map +1 -1
- package/dist/api/routes/projects.d.ts.map +1 -1
- package/dist/api/routes/projects.js +65 -0
- package/dist/api/routes/projects.js.map +1 -1
- package/dist/api/services/test-parser.d.ts +18 -0
- package/dist/api/services/test-parser.d.ts.map +1 -0
- package/dist/api/services/test-parser.js +200 -0
- package/dist/api/services/test-parser.js.map +1 -0
- package/dist/api/types.d.ts +15 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/core/database.d.ts +17 -0
- package/dist/core/database.js +29 -1
- package/dist/electron/core/database.d.ts +17 -0
- package/dist/electron/core/database.js +29 -1
- package/dist/electron/renderer/assets/index-ByHY-x-j.js +62 -0
- package/dist/electron/renderer/assets/index-CS-85xbL.css +1 -0
- package/dist/electron/renderer/assets/index-nts9ST-M.js +62 -0
- package/dist/electron/renderer/index.html +2 -2
- package/dist/electron/script-runner.js +51 -1
- package/dist/script-runner.js +51 -1
- package/dist/test-parser.d.ts +17 -0
- package/dist/test-parser.js +199 -0
- package/package.json +1 -1
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:*;" />
|
|
7
7
|
<title>projax</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-ByHY-x-j.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CS-85xbL.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -853,7 +853,7 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
853
853
|
};
|
|
854
854
|
addProcess(processInfo);
|
|
855
855
|
// Listen for process exit to clean up tracking
|
|
856
|
-
child.on('exit', (code, signal) => {
|
|
856
|
+
child.on('exit', async (code, signal) => {
|
|
857
857
|
// Remove from tracking when process exits
|
|
858
858
|
removeProcess(child.pid);
|
|
859
859
|
// Log exit information
|
|
@@ -863,6 +863,8 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
863
863
|
else if (signal !== null) {
|
|
864
864
|
fs.appendFileSync(logFile, `\n\n[Process killed by signal ${signal}]\n`);
|
|
865
865
|
}
|
|
866
|
+
// Check if this was a test script and parse results
|
|
867
|
+
await checkAndParseTestResults(logFile, projectPath, scriptName);
|
|
866
868
|
});
|
|
867
869
|
// Unref so parent can exit and process runs independently
|
|
868
870
|
child.unref();
|
|
@@ -922,3 +924,51 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
922
924
|
resolve(child.pid);
|
|
923
925
|
});
|
|
924
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Check log file for test output and parse results
|
|
929
|
+
*/
|
|
930
|
+
async function checkAndParseTestResults(logFile, projectPath, scriptName) {
|
|
931
|
+
try {
|
|
932
|
+
// Only parse if script name suggests it's a test command
|
|
933
|
+
const testScriptPatterns = ['test', 'spec', 'jest', 'vitest', 'mocha', 'pytest', 'unittest'];
|
|
934
|
+
const isTestScript = testScriptPatterns.some(pattern => scriptName.toLowerCase().includes(pattern));
|
|
935
|
+
if (!isTestScript) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
// Read the log file
|
|
939
|
+
if (!fs.existsSync(logFile)) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const logContent = fs.readFileSync(logFile, 'utf-8');
|
|
943
|
+
// Import test parser (dynamic to avoid circular dependency issues)
|
|
944
|
+
const testParserPath = path.join(__dirname, 'test-parser.js');
|
|
945
|
+
if (!fs.existsSync(testParserPath)) {
|
|
946
|
+
// Parser not available (might be in development mode)
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const { parseTestOutput, isTestOutput } = await Promise.resolve(`${testParserPath}`).then(s => __importStar(require(s)));
|
|
950
|
+
// Check if output contains test results
|
|
951
|
+
if (!isTestOutput(logContent)) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
// Parse test results
|
|
955
|
+
const parsed = parseTestOutput(logContent);
|
|
956
|
+
if (!parsed) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
// Get project from database
|
|
960
|
+
const db = (0, core_bridge_1.getDatabaseManager)();
|
|
961
|
+
const project = db.getProjectByPath(projectPath);
|
|
962
|
+
if (!project) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
// Save test results to database
|
|
966
|
+
db.addTestResult(project.id, scriptName, parsed.passed, parsed.failed, parsed.skipped, parsed.total, parsed.duration, parsed.coverage, parsed.framework, logContent.slice(-5000) // Store last 5000 chars of output
|
|
967
|
+
);
|
|
968
|
+
console.log(`\nš Test results saved: ${parsed.passed} passed, ${parsed.failed} failed, ${parsed.skipped} skipped`);
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
// Silently fail - test parsing is optional
|
|
972
|
+
console.error('Error parsing test results:', error);
|
|
973
|
+
}
|
|
974
|
+
}
|
package/dist/script-runner.js
CHANGED
|
@@ -853,7 +853,7 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
853
853
|
};
|
|
854
854
|
addProcess(processInfo);
|
|
855
855
|
// Listen for process exit to clean up tracking
|
|
856
|
-
child.on('exit', (code, signal) => {
|
|
856
|
+
child.on('exit', async (code, signal) => {
|
|
857
857
|
// Remove from tracking when process exits
|
|
858
858
|
removeProcess(child.pid);
|
|
859
859
|
// Log exit information
|
|
@@ -863,6 +863,8 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
863
863
|
else if (signal !== null) {
|
|
864
864
|
fs.appendFileSync(logFile, `\n\n[Process killed by signal ${signal}]\n`);
|
|
865
865
|
}
|
|
866
|
+
// Check if this was a test script and parse results
|
|
867
|
+
await checkAndParseTestResults(logFile, projectPath, scriptName);
|
|
866
868
|
});
|
|
867
869
|
// Unref so parent can exit and process runs independently
|
|
868
870
|
child.unref();
|
|
@@ -922,3 +924,51 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
922
924
|
resolve(child.pid);
|
|
923
925
|
});
|
|
924
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Check log file for test output and parse results
|
|
929
|
+
*/
|
|
930
|
+
async function checkAndParseTestResults(logFile, projectPath, scriptName) {
|
|
931
|
+
try {
|
|
932
|
+
// Only parse if script name suggests it's a test command
|
|
933
|
+
const testScriptPatterns = ['test', 'spec', 'jest', 'vitest', 'mocha', 'pytest', 'unittest'];
|
|
934
|
+
const isTestScript = testScriptPatterns.some(pattern => scriptName.toLowerCase().includes(pattern));
|
|
935
|
+
if (!isTestScript) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
// Read the log file
|
|
939
|
+
if (!fs.existsSync(logFile)) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const logContent = fs.readFileSync(logFile, 'utf-8');
|
|
943
|
+
// Import test parser (dynamic to avoid circular dependency issues)
|
|
944
|
+
const testParserPath = path.join(__dirname, 'test-parser.js');
|
|
945
|
+
if (!fs.existsSync(testParserPath)) {
|
|
946
|
+
// Parser not available (might be in development mode)
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const { parseTestOutput, isTestOutput } = await Promise.resolve(`${testParserPath}`).then(s => __importStar(require(s)));
|
|
950
|
+
// Check if output contains test results
|
|
951
|
+
if (!isTestOutput(logContent)) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
// Parse test results
|
|
955
|
+
const parsed = parseTestOutput(logContent);
|
|
956
|
+
if (!parsed) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
// Get project from database
|
|
960
|
+
const db = (0, core_bridge_1.getDatabaseManager)();
|
|
961
|
+
const project = db.getProjectByPath(projectPath);
|
|
962
|
+
if (!project) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
// Save test results to database
|
|
966
|
+
db.addTestResult(project.id, scriptName, parsed.passed, parsed.failed, parsed.skipped, parsed.total, parsed.duration, parsed.coverage, parsed.framework, logContent.slice(-5000) // Store last 5000 chars of output
|
|
967
|
+
);
|
|
968
|
+
console.log(`\nš Test results saved: ${parsed.passed} passed, ${parsed.failed} failed, ${parsed.skipped} skipped`);
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
// Silently fail - test parsing is optional
|
|
972
|
+
console.error('Error parsing test results:', error);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ParsedTestResult {
|
|
2
|
+
framework: string | null;
|
|
3
|
+
passed: number;
|
|
4
|
+
failed: number;
|
|
5
|
+
skipped: number;
|
|
6
|
+
total: number;
|
|
7
|
+
duration: number | null;
|
|
8
|
+
coverage: number | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse test output from various frameworks and extract statistics
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseTestOutput(output: string): ParsedTestResult | null;
|
|
14
|
+
/**
|
|
15
|
+
* Check if output appears to be from a test command
|
|
16
|
+
*/
|
|
17
|
+
export declare function isTestOutput(output: string): boolean;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Test output parser for extracting test statistics from common test runners
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.parseTestOutput = parseTestOutput;
|
|
5
|
+
exports.isTestOutput = isTestOutput;
|
|
6
|
+
/**
|
|
7
|
+
* Parse test output from various frameworks and extract statistics
|
|
8
|
+
*/
|
|
9
|
+
function parseTestOutput(output) {
|
|
10
|
+
const result = {
|
|
11
|
+
framework: null,
|
|
12
|
+
passed: 0,
|
|
13
|
+
failed: 0,
|
|
14
|
+
skipped: 0,
|
|
15
|
+
total: 0,
|
|
16
|
+
duration: null,
|
|
17
|
+
coverage: null,
|
|
18
|
+
};
|
|
19
|
+
// Detect framework and parse accordingly
|
|
20
|
+
if (output.includes('PASS') && output.includes('FAIL') || output.includes('Test Suites:')) {
|
|
21
|
+
// Jest/Vitest
|
|
22
|
+
result.framework = parseJestVitestOutput(output, result);
|
|
23
|
+
}
|
|
24
|
+
else if (output.includes('passing') && output.includes('failing')) {
|
|
25
|
+
// Mocha
|
|
26
|
+
result.framework = parseMochaOutput(output, result);
|
|
27
|
+
}
|
|
28
|
+
else if (output.includes('passed') && output.includes('failed') && output.includes('pytest')) {
|
|
29
|
+
// pytest
|
|
30
|
+
result.framework = parsePytestOutput(output, result);
|
|
31
|
+
}
|
|
32
|
+
else if (output.includes('OK') && output.includes('test')) {
|
|
33
|
+
// Python unittest
|
|
34
|
+
result.framework = parseUnittestOutput(output, result);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Try generic parsing
|
|
38
|
+
return parseGenericOutput(output);
|
|
39
|
+
}
|
|
40
|
+
// Calculate total if not set
|
|
41
|
+
if (result.total === 0) {
|
|
42
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
43
|
+
}
|
|
44
|
+
return result.total > 0 ? result : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse Jest/Vitest output
|
|
48
|
+
*/
|
|
49
|
+
function parseJestVitestOutput(output, result) {
|
|
50
|
+
// Jest format:
|
|
51
|
+
// Test Suites: 2 failed, 1 passed, 3 total
|
|
52
|
+
// Tests: 5 failed, 10 passed, 15 total
|
|
53
|
+
// Time: 2.345 s
|
|
54
|
+
const testSuitesMatch = output.match(/Test Suites:.*?(\d+)\s+passed/i);
|
|
55
|
+
const testsMatch = output.match(/Tests:\s+(?:(\d+)\s+failed,?\s*)?(?:(\d+)\s+skipped,?\s*)?(?:(\d+)\s+passed)/i);
|
|
56
|
+
const totalMatch = output.match(/Tests:.*?(\d+)\s+total/i);
|
|
57
|
+
const timeMatch = output.match(/Time:\s+([\d.]+)\s*s/i);
|
|
58
|
+
const coverageMatch = output.match(/All files\s*\|\s*([\d.]+)/);
|
|
59
|
+
if (testsMatch) {
|
|
60
|
+
result.failed = testsMatch[1] ? parseInt(testsMatch[1], 10) : 0;
|
|
61
|
+
result.skipped = testsMatch[2] ? parseInt(testsMatch[2], 10) : 0;
|
|
62
|
+
result.passed = testsMatch[3] ? parseInt(testsMatch[3], 10) : 0;
|
|
63
|
+
}
|
|
64
|
+
if (totalMatch) {
|
|
65
|
+
result.total = parseInt(totalMatch[1], 10);
|
|
66
|
+
}
|
|
67
|
+
if (timeMatch) {
|
|
68
|
+
result.duration = Math.floor(parseFloat(timeMatch[1]) * 1000);
|
|
69
|
+
}
|
|
70
|
+
if (coverageMatch) {
|
|
71
|
+
result.coverage = parseFloat(coverageMatch[1]);
|
|
72
|
+
}
|
|
73
|
+
// Determine if Jest or Vitest
|
|
74
|
+
return output.includes('vitest') ? 'vitest' : 'jest';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse Mocha output
|
|
78
|
+
*/
|
|
79
|
+
function parseMochaOutput(output, result) {
|
|
80
|
+
// Mocha format:
|
|
81
|
+
// 10 passing (234ms)
|
|
82
|
+
// 2 failing
|
|
83
|
+
// 1 pending
|
|
84
|
+
const passingMatch = output.match(/(\d+)\s+passing/i);
|
|
85
|
+
const failingMatch = output.match(/(\d+)\s+failing/i);
|
|
86
|
+
const pendingMatch = output.match(/(\d+)\s+pending/i);
|
|
87
|
+
const timeMatch = output.match(/passing\s+\((\d+)ms\)/i);
|
|
88
|
+
if (passingMatch) {
|
|
89
|
+
result.passed = parseInt(passingMatch[1], 10);
|
|
90
|
+
}
|
|
91
|
+
if (failingMatch) {
|
|
92
|
+
result.failed = parseInt(failingMatch[1], 10);
|
|
93
|
+
}
|
|
94
|
+
if (pendingMatch) {
|
|
95
|
+
result.skipped = parseInt(pendingMatch[1], 10);
|
|
96
|
+
}
|
|
97
|
+
if (timeMatch) {
|
|
98
|
+
result.duration = parseInt(timeMatch[1], 10);
|
|
99
|
+
}
|
|
100
|
+
return 'mocha';
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse pytest output
|
|
104
|
+
*/
|
|
105
|
+
function parsePytestOutput(output, result) {
|
|
106
|
+
// pytest format:
|
|
107
|
+
// ===== 10 passed, 2 failed, 1 skipped in 2.34s =====
|
|
108
|
+
const statsMatch = output.match(/=+\s*(?:(\d+)\s+passed)?(?:,\s*(\d+)\s+failed)?(?:,\s*(\d+)\s+skipped)?(?:\s+in\s+([\d.]+)s)?/i);
|
|
109
|
+
if (statsMatch) {
|
|
110
|
+
result.passed = statsMatch[1] ? parseInt(statsMatch[1], 10) : 0;
|
|
111
|
+
result.failed = statsMatch[2] ? parseInt(statsMatch[2], 10) : 0;
|
|
112
|
+
result.skipped = statsMatch[3] ? parseInt(statsMatch[3], 10) : 0;
|
|
113
|
+
if (statsMatch[4]) {
|
|
114
|
+
result.duration = Math.floor(parseFloat(statsMatch[4]) * 1000);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Coverage
|
|
118
|
+
const coverageMatch = output.match(/TOTAL\s+\d+\s+\d+\s+(\d+)%/);
|
|
119
|
+
if (coverageMatch) {
|
|
120
|
+
result.coverage = parseInt(coverageMatch[1], 10);
|
|
121
|
+
}
|
|
122
|
+
return 'pytest';
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse Python unittest output
|
|
126
|
+
*/
|
|
127
|
+
function parseUnittestOutput(output, result) {
|
|
128
|
+
// unittest format:
|
|
129
|
+
// Ran 15 tests in 2.345s
|
|
130
|
+
// OK
|
|
131
|
+
// or FAILED (failures=2, errors=1)
|
|
132
|
+
const ranMatch = output.match(/Ran\s+(\d+)\s+tests?\s+in\s+([\d.]+)s/i);
|
|
133
|
+
const failedMatch = output.match(/FAILED\s*\((?:failures=(\d+))?(?:,\s*errors=(\d+))?\)/i);
|
|
134
|
+
if (ranMatch) {
|
|
135
|
+
result.total = parseInt(ranMatch[1], 10);
|
|
136
|
+
result.duration = Math.floor(parseFloat(ranMatch[2]) * 1000);
|
|
137
|
+
}
|
|
138
|
+
if (failedMatch) {
|
|
139
|
+
const failures = failedMatch[1] ? parseInt(failedMatch[1], 10) : 0;
|
|
140
|
+
const errors = failedMatch[2] ? parseInt(failedMatch[2], 10) : 0;
|
|
141
|
+
result.failed = failures + errors;
|
|
142
|
+
result.passed = result.total - result.failed;
|
|
143
|
+
}
|
|
144
|
+
else if (output.includes('OK')) {
|
|
145
|
+
result.passed = result.total;
|
|
146
|
+
}
|
|
147
|
+
return 'unittest';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Generic parser for unrecognized frameworks
|
|
151
|
+
*/
|
|
152
|
+
function parseGenericOutput(output) {
|
|
153
|
+
const result = {
|
|
154
|
+
framework: 'unknown',
|
|
155
|
+
passed: 0,
|
|
156
|
+
failed: 0,
|
|
157
|
+
skipped: 0,
|
|
158
|
+
total: 0,
|
|
159
|
+
duration: null,
|
|
160
|
+
coverage: null,
|
|
161
|
+
};
|
|
162
|
+
// Try to find common patterns
|
|
163
|
+
const patterns = [
|
|
164
|
+
{ regex: /(\d+)\s+(?:test(?:s)?|spec(?:s)?)\s+passed/i, key: 'passed' },
|
|
165
|
+
{ regex: /(\d+)\s+(?:test(?:s)?|spec(?:s)?)\s+failed/i, key: 'failed' },
|
|
166
|
+
{ regex: /(\d+)\s+(?:test(?:s)?|spec(?:s)?)\s+skipped/i, key: 'skipped' },
|
|
167
|
+
{ regex: /ā\s*(\d+)/i, key: 'passed' },
|
|
168
|
+
{ regex: /ā\s*(\d+)/i, key: 'failed' },
|
|
169
|
+
];
|
|
170
|
+
for (const pattern of patterns) {
|
|
171
|
+
const match = output.match(pattern.regex);
|
|
172
|
+
if (match && pattern.key in result) {
|
|
173
|
+
result[pattern.key] = parseInt(match[1], 10);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
result.total = result.passed + result.failed + result.skipped;
|
|
177
|
+
return result.total > 0 ? result : null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if output appears to be from a test command
|
|
181
|
+
*/
|
|
182
|
+
function isTestOutput(output) {
|
|
183
|
+
const testIndicators = [
|
|
184
|
+
'Test Suites:',
|
|
185
|
+
'Tests:',
|
|
186
|
+
'passing',
|
|
187
|
+
'failing',
|
|
188
|
+
'passed',
|
|
189
|
+
'failed',
|
|
190
|
+
'skipped',
|
|
191
|
+
'pytest',
|
|
192
|
+
'Ran',
|
|
193
|
+
'test',
|
|
194
|
+
'spec',
|
|
195
|
+
'PASS',
|
|
196
|
+
'FAIL',
|
|
197
|
+
];
|
|
198
|
+
return testIndicators.some(indicator => output.includes(indicator));
|
|
199
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projax",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.27",
|
|
4
4
|
"description": "Cross-platform project management dashboard for tracking local development projects. Features CLI, Terminal UI, Desktop app, REST API, and built-in tools for test detection, port management, and script execution.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|