deflake 1.2.25 → 1.2.28

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 (2) hide show
  1. package/cli.js +84 -21
  2. 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, { shell: true, stdio: 'inherit' });
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)) return;
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(`\nāš ļø Scan Error in ${dir}: ${C.RED}${e.message}${C.RESET}`);
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(`šŸ” Analyzing ${artifacts.length} failure(s)...`);
107
+ console.log(`${C.BRIGHT}šŸ” Analyzing ${artifacts.length} failure(s)...${C.RESET}\n`);
96
108
  let count = 0;
97
- for (const art of artifacts) {
98
- process.stdout.write(`ā³ Analyzing ${art.name}...`);
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
- process.stdout.write(`\rāœ… Analysis complete for ${art.name}\n`);
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) console.log(` ${C.CYAN}${C.BRIGHT}🧠 Insight:${C.RESET} ${C.GRAY}${parsed.explanation}${C.RESET}`);
110
- if (argv.fix && parsed.patches && parsed.patches.length > 0) {
111
- if (await applyFix(res, loc)) {
112
- count++;
113
- console.log(` ${C.GREEN}šŸ’‰ Fix applied to ${path.basename(loc.path)}:${loc.line}${C.RESET}`);
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}`);
114
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}`);
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}${C.BRIGHT}🧠 Insight:${C.RESET} ${res.fix}`);
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
- process.stdout.write(`\rāŒ Analysis incomplete for ${art.name}: ${res?.message || 'Check logs'}\n`);
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(`\n āŒ ${C.RED}Error: ${e.message}${C.RESET}`);
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 indent = lines[idx].match(/^\s*/)[0];
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') lines.splice(p.line, 0, cleanLine);
155
- else lines[idx] = cleanLine;
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
  }
@@ -169,9 +222,19 @@ async function applyFix(res, loc) {
169
222
  }
170
223
 
171
224
  function extractLoc(text) {
172
- // Support .ts, .js, .cy.ts, .cy.js, .spec.ts, .spec.js
173
- const m = text.match(/at\s+(.*?\.(?:cy\.)?(?:spec\.)?(?:ts|js)):(\d+):(\d+)/);
174
- return m ? { path: path.resolve(m[1]), line: parseInt(m[2]) } : null;
225
+ // Strategy 1: Absolute path inside parentheses — at Function (/absolute/path/file.ts:75:35)
226
+ let m = text.match(/\(([^()]+\.(?:ts|js)):(\d+)(?::(\d+))?\)/);
227
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
228
+
229
+ // Strategy 2: Absolute or relative path after "at" — at /path/file.ts:75:35 or at ../pages/file.ts:75
230
+ m = text.match(/at\s+([^\s(]+\.(?:ts|js)):(\d+)(?::(\d+))?/);
231
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
232
+
233
+ // Strategy 3: Any path ending in .ts/.js with a line number (broadest match)
234
+ m = text.match(/([\w.\/\\-]+\.(?:ts|js)):(\d+)(?::(\d+))?/);
235
+ if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
236
+
237
+ return null;
175
238
  }
176
239
 
177
240
  async function runDoctor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.2.25",
3
+ "version": "1.2.28",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {