deflake 1.2.25 ā 1.2.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/cli.js +71 -18
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -63,27 +63,39 @@ async function main() {
|
|
|
63
63
|
async function runNative(fullCommand) {
|
|
64
64
|
return new Promise((resolve) => {
|
|
65
65
|
console.log(`š Running command: ${C.CYAN}${fullCommand}${C.RESET}`);
|
|
66
|
-
const child = spawn(fullCommand, {
|
|
66
|
+
const child = spawn(fullCommand, {
|
|
67
|
+
shell: true,
|
|
68
|
+
stdio: 'inherit',
|
|
69
|
+
env: { ...process.env, PLAYWRIGHT_HTML_OPEN: 'never' }
|
|
70
|
+
});
|
|
67
71
|
child.on('close', (code) => resolve({ code }));
|
|
68
72
|
});
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
function detectFailures() {
|
|
72
76
|
const results = [];
|
|
77
|
+
console.log(`\n${C.CYAN}š Scanning for failure artifacts...${C.RESET}`);
|
|
73
78
|
['test-results', 'playwright-report'].forEach(dir => {
|
|
74
|
-
if (!fs.existsSync(dir))
|
|
79
|
+
if (!fs.existsSync(dir)) {
|
|
80
|
+
console.log(` ${C.GRAY}āā ${dir}/: not found (skipping)${C.RESET}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
75
83
|
try {
|
|
76
84
|
const files = fs.readdirSync(dir, { recursive: true });
|
|
85
|
+
let found = 0;
|
|
77
86
|
files.forEach(f => {
|
|
78
87
|
const fullPath = path.join(dir, f);
|
|
79
88
|
if (f.endsWith('error-context.md')) {
|
|
80
89
|
results.push({ name: path.basename(path.dirname(fullPath)), htmlPath: fullPath });
|
|
90
|
+
found++;
|
|
81
91
|
}
|
|
82
92
|
});
|
|
93
|
+
console.log(` ${C.GRAY}āā ${dir}/:${C.RESET} ${found > 0 ? C.GREEN + found + ' failure artifact(s)' : C.GRAY + 'no failures'}${C.RESET}`);
|
|
83
94
|
} catch (e) {
|
|
84
|
-
console.log(
|
|
95
|
+
console.log(` ${C.GRAY}āā ${dir}/:${C.RESET} ${C.RED}SCAN ERROR ā ${e.message}${C.RESET}`);
|
|
85
96
|
}
|
|
86
97
|
});
|
|
98
|
+
console.log(` ${C.GRAY}āā Total:${C.RESET} ${C.BRIGHT}${results.length} artifact(s) to analyze${C.RESET}\n`);
|
|
87
99
|
return results;
|
|
88
100
|
}
|
|
89
101
|
|
|
@@ -92,38 +104,72 @@ async function analyzeAndFix(artifacts, client, argv) {
|
|
|
92
104
|
console.log(` ${C.YELLOW}ā ļø No failure artifacts detected. Run 'npx deflake doctor' to check permissions.${C.RESET}`);
|
|
93
105
|
return 0;
|
|
94
106
|
}
|
|
95
|
-
console.log(
|
|
107
|
+
console.log(`${C.BRIGHT}š Analyzing ${artifacts.length} failure(s)...${C.RESET}\n`);
|
|
96
108
|
let count = 0;
|
|
97
|
-
for (
|
|
98
|
-
|
|
109
|
+
for (let i = 0; i < artifacts.length; i++) {
|
|
110
|
+
const art = artifacts[i];
|
|
111
|
+
console.log(`${C.CYAN}āāā [${i+1}/${artifacts.length}] ${art.name} āāā${C.RESET}`);
|
|
99
112
|
try {
|
|
100
113
|
const content = fs.readFileSync(art.htmlPath, 'utf8');
|
|
101
114
|
const loc = extractLoc(content);
|
|
115
|
+
|
|
116
|
+
// Step 1: Location extraction
|
|
117
|
+
if (loc) {
|
|
118
|
+
console.log(` ${C.GRAY}š Location:${C.RESET} ${path.basename(loc.path)}:${loc.line}`);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(` ${C.YELLOW}š Location: Could not extract (regex mismatch)${C.RESET}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 2: Source code reading
|
|
102
124
|
const source = loc && fs.existsSync(loc.path) ? fs.readFileSync(loc.path, 'utf8') : null;
|
|
125
|
+
console.log(` ${C.GRAY}š Source:${C.RESET} ${source ? 'Loaded (' + source.split('\n').length + ' lines)' : C.YELLOW + 'Not available' + C.RESET}`);
|
|
126
|
+
|
|
127
|
+
// Step 3: API call
|
|
128
|
+
console.log(` ${C.GRAY}š Calling DeFlake API...${C.RESET}`);
|
|
103
129
|
const res = await client.heal(null, art.htmlPath, loc, source, argv.fix);
|
|
104
130
|
|
|
105
131
|
if (res && res.status === 'success') {
|
|
106
|
-
|
|
132
|
+
console.log(` ${C.GREEN}ā
API Response: SUCCESS${C.RESET}`);
|
|
107
133
|
try {
|
|
108
134
|
const parsed = JSON.parse(res.fix);
|
|
109
|
-
if (parsed.explanation)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
135
|
+
if (parsed.explanation) {
|
|
136
|
+
console.log(` ${C.CYAN}š§ Insight:${C.RESET} ${C.GRAY}${parsed.explanation}${C.RESET}`);
|
|
137
|
+
}
|
|
138
|
+
if (parsed.patches && parsed.patches.length > 0) {
|
|
139
|
+
console.log(` ${C.GRAY}𩹠Patches:${C.RESET} ${parsed.patches.length} patch(es) suggested`);
|
|
140
|
+
for (const p of parsed.patches) {
|
|
141
|
+
console.log(` ${C.GRAY}ā ${p.action} at line ${p.line}: ${p.new_line?.substring(0, 70)}${C.RESET}`);
|
|
142
|
+
}
|
|
143
|
+
if (argv.fix) {
|
|
144
|
+
console.log(` ${C.BRIGHT}š Applying patches...${C.RESET}`);
|
|
145
|
+
if (await applyFix(res, loc)) {
|
|
146
|
+
count++;
|
|
147
|
+
console.log(` ${C.GREEN}${C.BRIGHT}ā
Fix applied to ${path.basename(loc.path)}:${loc.line}${C.RESET}`);
|
|
148
|
+
} else {
|
|
149
|
+
console.log(` ${C.YELLOW}ā ļø Patches could not be applied (see details above)${C.RESET}`);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
console.log(` ${C.YELLOW}ā¹ļø Use --fix flag to auto-apply these patches${C.RESET}`);
|
|
114
153
|
}
|
|
154
|
+
} else {
|
|
155
|
+
console.log(` ${C.YELLOW}ā ļø API returned no patches${C.RESET}`);
|
|
115
156
|
}
|
|
116
157
|
} catch (e) {
|
|
117
|
-
console.log(` ${C.CYAN}
|
|
158
|
+
console.log(` ${C.CYAN}š§ Insight:${C.RESET} ${res.fix}`);
|
|
118
159
|
}
|
|
160
|
+
} else if (res && res.detail === 'QUOTA_EXCEEDED') {
|
|
161
|
+
console.log(` ${C.RED}š« QUOTA EXCEEDED ā No more fixes available this month${C.RESET}`);
|
|
119
162
|
} else {
|
|
120
|
-
|
|
163
|
+
console.log(` ${C.RED}ā API Response: ${res?.status || 'null'} ā ${res?.message || res?.detail || 'Unknown error'}${C.RESET}`);
|
|
121
164
|
}
|
|
122
165
|
} catch (e) {
|
|
123
|
-
console.log(
|
|
166
|
+
console.log(` ${C.RED}ā Error: ${e.message}${C.RESET}`);
|
|
124
167
|
}
|
|
168
|
+
console.log('');
|
|
125
169
|
}
|
|
170
|
+
console.log(`${C.BRIGHT}āāā DeFlake Summary: ${count}/${artifacts.length} fix(es) applied āāā${C.RESET}\n`);
|
|
126
171
|
return count;
|
|
172
|
+
|
|
127
173
|
}
|
|
128
174
|
|
|
129
175
|
async function applyFix(res, loc) {
|
|
@@ -144,15 +190,22 @@ async function applyFix(res, loc) {
|
|
|
144
190
|
const lines = content.split('\n');
|
|
145
191
|
const idx = p.line - 1;
|
|
146
192
|
if (idx >= 0 && idx < lines.length) {
|
|
147
|
-
const
|
|
193
|
+
const originalLine = lines[idx];
|
|
194
|
+
const indent = originalLine.match(/^\s*/)[0];
|
|
148
195
|
const cleanLine = indent + p.new_line.trim();
|
|
149
196
|
// Safety: skip if the new line is already present (dedup)
|
|
150
197
|
if (content.includes(p.new_line.trim())) {
|
|
151
198
|
console.log(` ${C.GRAY}āļø Skipped (already present): ${p.new_line.trim().substring(0, 60)}...${C.RESET}`);
|
|
152
199
|
continue;
|
|
153
200
|
}
|
|
154
|
-
if (p.action === 'INSERT_AFTER')
|
|
155
|
-
|
|
201
|
+
if (p.action === 'INSERT_AFTER') {
|
|
202
|
+
lines.splice(p.line, 0, cleanLine);
|
|
203
|
+
console.log(` ${C.GREEN}+ Line ${p.line + 1}: ${cleanLine.trim()}${C.RESET}`);
|
|
204
|
+
} else {
|
|
205
|
+
lines[idx] = cleanLine;
|
|
206
|
+
console.log(` ${C.RED}- Line ${p.line}: ${originalLine.trim()}${C.RESET}`);
|
|
207
|
+
console.log(` ${C.GREEN}+ Line ${p.line}: ${cleanLine.trim()}${C.RESET}`);
|
|
208
|
+
}
|
|
156
209
|
content = lines.join('\n');
|
|
157
210
|
appliedCount++;
|
|
158
211
|
}
|