deflake 1.2.21 β 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/README.md +6 -3
- package/cli.js +158 -94
- package/client.js +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,13 +22,16 @@ npx deflake npx playwright test
|
|
|
22
22
|
npx deflake npx cypress run
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
### π Diagnostics (Doctor Mode)
|
|
26
|
-
Validate your environment, API key, and quota without running tests:
|
|
27
|
-
|
|
28
25
|
```bash
|
|
29
26
|
npx deflake doctor
|
|
30
27
|
```
|
|
31
28
|
|
|
29
|
+
### π macOS Permission Requirements
|
|
30
|
+
On macOS, if your project folder is located in **Documents** or **Desktop**, DeFlake may be blocked from reading test results.
|
|
31
|
+
|
|
32
|
+
* **Move Project**: Move your folder out of Documents (e.g., to `/Users/hugo/code/`).
|
|
33
|
+
* **Full Disk Access**: Grant "Full Disk Access" to your Terminal/iTerm in *System Settings > Privacy & Security*.
|
|
34
|
+
|
|
32
35
|
### Manual Mode
|
|
33
36
|
Analyze an existing error log and HTML/Image snapshot:
|
|
34
37
|
|
package/cli.js
CHANGED
|
@@ -18,8 +18,10 @@ const C = {
|
|
|
18
18
|
BLUE: "\x1b[34m",
|
|
19
19
|
MAGENTA: "\x1b[35m",
|
|
20
20
|
GRAY: "\x1b[90m",
|
|
21
|
+
WHITE: "\x1b[37m",
|
|
21
22
|
BG_BLUE: "\x1b[44m",
|
|
22
|
-
BG_GREEN: "\x1b[42m"
|
|
23
|
+
BG_GREEN: "\x1b[42m",
|
|
24
|
+
BG_RED: "\x1b[41m"
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
const rawArgs = process.argv.slice(2);
|
|
@@ -61,25 +63,39 @@ async function main() {
|
|
|
61
63
|
async function runNative(fullCommand) {
|
|
62
64
|
return new Promise((resolve) => {
|
|
63
65
|
console.log(`π Running command: ${C.CYAN}${fullCommand}${C.RESET}`);
|
|
64
|
-
const child = spawn(fullCommand, {
|
|
66
|
+
const child = spawn(fullCommand, {
|
|
67
|
+
shell: true,
|
|
68
|
+
stdio: 'inherit',
|
|
69
|
+
env: { ...process.env, PLAYWRIGHT_HTML_OPEN: 'never' }
|
|
70
|
+
});
|
|
65
71
|
child.on('close', (code) => resolve({ code }));
|
|
66
72
|
});
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
function detectFailures() {
|
|
70
76
|
const results = [];
|
|
77
|
+
console.log(`\n${C.CYAN}π Scanning for failure artifacts...${C.RESET}`);
|
|
71
78
|
['test-results', 'playwright-report'].forEach(dir => {
|
|
72
|
-
if (!fs.existsSync(dir))
|
|
79
|
+
if (!fs.existsSync(dir)) {
|
|
80
|
+
console.log(` ${C.GRAY}ββ ${dir}/: not found (skipping)${C.RESET}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
73
83
|
try {
|
|
74
84
|
const files = fs.readdirSync(dir, { recursive: true });
|
|
85
|
+
let found = 0;
|
|
75
86
|
files.forEach(f => {
|
|
76
87
|
const fullPath = path.join(dir, f);
|
|
77
88
|
if (f.endsWith('error-context.md')) {
|
|
78
89
|
results.push({ name: path.basename(path.dirname(fullPath)), htmlPath: fullPath });
|
|
90
|
+
found++;
|
|
79
91
|
}
|
|
80
92
|
});
|
|
81
|
-
|
|
93
|
+
console.log(` ${C.GRAY}ββ ${dir}/:${C.RESET} ${found > 0 ? C.GREEN + found + ' failure artifact(s)' : C.GRAY + 'no failures'}${C.RESET}`);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.log(` ${C.GRAY}ββ ${dir}/:${C.RESET} ${C.RED}SCAN ERROR β ${e.message}${C.RESET}`);
|
|
96
|
+
}
|
|
82
97
|
});
|
|
98
|
+
console.log(` ${C.GRAY}ββ Total:${C.RESET} ${C.BRIGHT}${results.length} artifact(s) to analyze${C.RESET}\n`);
|
|
83
99
|
return results;
|
|
84
100
|
}
|
|
85
101
|
|
|
@@ -88,82 +104,140 @@ async function analyzeAndFix(artifacts, client, argv) {
|
|
|
88
104
|
console.log(` ${C.YELLOW}β οΈ No failure artifacts detected. Run 'npx deflake doctor' to check permissions.${C.RESET}`);
|
|
89
105
|
return 0;
|
|
90
106
|
}
|
|
91
|
-
console.log(
|
|
107
|
+
console.log(`${C.BRIGHT}π Analyzing ${artifacts.length} failure(s)...${C.RESET}\n`);
|
|
92
108
|
let count = 0;
|
|
93
|
-
for (
|
|
94
|
-
|
|
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}`);
|
|
95
112
|
try {
|
|
96
113
|
const content = fs.readFileSync(art.htmlPath, 'utf8');
|
|
97
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
|
|
98
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}`);
|
|
99
129
|
const res = await client.heal(null, art.htmlPath, loc, source, argv.fix);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
|
|
131
|
+
if (res && res.status === 'success') {
|
|
132
|
+
console.log(` ${C.GREEN}β
API Response: SUCCESS${C.RESET}`);
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(res.fix);
|
|
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}`);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
console.log(` ${C.YELLOW}β οΈ API returned no patches${C.RESET}`);
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.log(` ${C.CYAN}π§ Insight:${C.RESET} ${res.fix}`);
|
|
159
|
+
}
|
|
160
|
+
} else if (res && res.detail === 'QUOTA_EXCEEDED') {
|
|
161
|
+
console.log(` ${C.RED}π« QUOTA EXCEEDED β No more fixes available this month${C.RESET}`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(` ${C.RED}β API Response: ${res?.status || 'null'} β ${res?.message || res?.detail || 'Unknown error'}${C.RESET}`);
|
|
104
164
|
}
|
|
105
165
|
} catch (e) {
|
|
106
|
-
console.log(
|
|
166
|
+
console.log(` ${C.RED}β Error: ${e.message}${C.RESET}`);
|
|
107
167
|
}
|
|
168
|
+
console.log('');
|
|
108
169
|
}
|
|
170
|
+
console.log(`${C.BRIGHT}βββ DeFlake Summary: ${count}/${artifacts.length} fix(es) applied βββ${C.RESET}\n`);
|
|
109
171
|
return count;
|
|
172
|
+
|
|
110
173
|
}
|
|
111
174
|
|
|
112
175
|
async function applyFix(res, loc) {
|
|
113
|
-
if (!loc?.path)
|
|
176
|
+
if (!loc?.path) {
|
|
177
|
+
console.log(` ${C.YELLOW}β οΈ Cannot apply fix: failure location not detected.${C.RESET}`);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
114
180
|
try {
|
|
115
181
|
const patches = JSON.parse(res.fix).patches || [];
|
|
182
|
+
if (patches.length === 0) {
|
|
183
|
+
console.log(` ${C.YELLOW}β οΈ No patches provided by AI.${C.RESET}`);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
116
186
|
const original = fs.readFileSync(loc.path, 'utf8');
|
|
117
187
|
let content = original;
|
|
188
|
+
let appliedCount = 0;
|
|
118
189
|
for (const p of patches) {
|
|
119
190
|
const lines = content.split('\n');
|
|
120
191
|
const idx = p.line - 1;
|
|
121
192
|
if (idx >= 0 && idx < lines.length) {
|
|
122
|
-
const
|
|
193
|
+
const originalLine = lines[idx];
|
|
194
|
+
const indent = originalLine.match(/^\s*/)[0];
|
|
123
195
|
const cleanLine = indent + p.new_line.trim();
|
|
124
|
-
if
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
content = lines.join('\n');
|
|
196
|
+
// Safety: skip if the new line is already present (dedup)
|
|
197
|
+
if (content.includes(p.new_line.trim())) {
|
|
198
|
+
console.log(` ${C.GRAY}βοΈ Skipped (already present): ${p.new_line.trim().substring(0, 60)}...${C.RESET}`);
|
|
199
|
+
continue;
|
|
129
200
|
}
|
|
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
|
+
}
|
|
209
|
+
content = lines.join('\n');
|
|
210
|
+
appliedCount++;
|
|
130
211
|
}
|
|
131
212
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (content.match(/class\s+\w+\s+\{[\s\S]*?await\s+/)) throw new Error("Illegal await");
|
|
213
|
+
if (appliedCount > 0) {
|
|
214
|
+
fs.writeFileSync(loc.path, content);
|
|
135
215
|
return true;
|
|
136
|
-
} catch(e) {
|
|
137
|
-
fs.writeFileSync(loc.path, original);
|
|
138
|
-
return false;
|
|
139
216
|
}
|
|
140
|
-
|
|
217
|
+
return false;
|
|
218
|
+
} catch(e) {
|
|
219
|
+
console.log(` ${C.RED}β Patch error: ${e.message}${C.RESET}`);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
141
222
|
}
|
|
142
223
|
|
|
143
224
|
function extractLoc(text) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function printFix(fix, loc) {
|
|
149
|
-
try {
|
|
150
|
-
const p = JSON.parse(fix);
|
|
151
|
-
console.log(`\n${C.CYAN}${C.BRIGHT}π‘ SUGGESTED FIX:${C.RESET}`);
|
|
152
|
-
console.log(` ${C.BRIGHT}Reason:${C.RESET} ${p.explanation}`);
|
|
153
|
-
} catch(e) {}
|
|
225
|
+
// Support .ts, .js, .cy.ts, .cy.js, .spec.ts, .spec.js
|
|
226
|
+
const m = text.match(/at\s+(.*?\.(?:cy\.)?(?:spec\.)?(?:ts|js)):(\d+):(\d+)/);
|
|
227
|
+
return m ? { path: path.resolve(m[1]), line: parseInt(m[2]) } : null;
|
|
154
228
|
}
|
|
155
229
|
|
|
156
230
|
async function runDoctor() {
|
|
157
|
-
console.log(`\n${C.BG_BLUE}${C.WHITE}${C.BRIGHT} DEFLAKE MISSION CONTROL - DIAGNOSTIC
|
|
231
|
+
console.log(`\n${C.BG_BLUE}${C.WHITE}${C.BRIGHT} DEFLAKE MISSION CONTROL - DEEP DIAGNOSTIC ${C.RESET}\n`);
|
|
158
232
|
|
|
159
|
-
// 1. SYSTEM
|
|
233
|
+
// 1. SYSTEM CORE
|
|
160
234
|
console.log(`${C.CYAN}1. SYSTEM CORE${C.RESET}`);
|
|
161
235
|
console.log(` ${C.GRAY}ββ Version: ${C.RESET}${C.BRIGHT}v${pkg.version}${C.RESET}`);
|
|
162
236
|
console.log(` ${C.GRAY}ββ Platform: ${C.RESET}${process.platform} (${process.arch})`);
|
|
163
237
|
console.log(` ${C.GRAY}ββ Project: ${C.RESET}${path.basename(process.cwd())}\n`);
|
|
164
238
|
|
|
165
|
-
// 2. PROJECT AUDIT
|
|
166
|
-
console.log(`${C.CYAN}2. PROJECT
|
|
239
|
+
// 2. PROJECT AUDIT
|
|
240
|
+
console.log(`${C.CYAN}2. PROJECT AUDIT${C.RESET}`);
|
|
167
241
|
const fw = DeFlakeClient.detectFramework();
|
|
168
242
|
console.log(` ${C.GRAY}ββ Framework: ${C.RESET}${C.GREEN}${fw.toUpperCase()}${C.RESET}`);
|
|
169
243
|
|
|
@@ -175,95 +249,85 @@ async function runDoctor() {
|
|
|
175
249
|
files.forEach(f => allFiles.push({ name: f, full: path.join(d, f) }));
|
|
176
250
|
}
|
|
177
251
|
});
|
|
178
|
-
|
|
179
|
-
if (allFiles.length
|
|
180
|
-
|
|
181
|
-
allFiles.slice(0, 8).forEach(file => {
|
|
182
|
-
process.stdout.write(` ${C.GRAY}β ββ ${path.basename(file.name).padEnd(25)}${C.RESET} `);
|
|
183
|
-
try {
|
|
184
|
-
execSync(`node --check "${file.full}"`, { stdio: 'ignore' });
|
|
185
|
-
console.log(`${C.GREEN}β Syntax OK${C.RESET}`);
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.log(`${C.RED}β SYNTAX ERROR${C.RESET}`);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
if (allFiles.length > 8) console.log(` ${C.GRAY}β ββ ... and ${allFiles.length - 8} more files validated.${C.RESET}`);
|
|
191
|
-
else console.log(` ${C.GRAY}β ββ All source files validated.${C.RESET}`);
|
|
192
|
-
} else {
|
|
193
|
-
console.log(` ${C.GRAY}ββ Discovery: ${C.RED}No test/page files found!${C.RESET}`);
|
|
194
|
-
}
|
|
252
|
+
console.log(` ${C.GRAY}ββ Discovery: ${C.RESET}${C.GREEN}${allFiles.length} files matched${C.RESET}`);
|
|
253
|
+
if (allFiles.length === 0) console.log(` ${C.GRAY}ββ ${C.RED}Warning: No test files found in expected directories.${C.RESET}`);
|
|
254
|
+
else console.log(` ${C.GRAY}ββ All source folders are accessible.${C.RESET}`);
|
|
195
255
|
console.log("");
|
|
196
256
|
|
|
197
|
-
// 3. API
|
|
198
|
-
console.log(`${C.CYAN}3. API
|
|
257
|
+
// 3. API CONNECTIVITY
|
|
258
|
+
console.log(`${C.CYAN}3. API CONNECTIVITY${C.RESET}`);
|
|
199
259
|
const apiKey = process.env.DEFLAKE_API_KEY || "NOT_SET";
|
|
200
|
-
const masked = apiKey === "NOT_SET" ? apiKey : (apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length - 4));
|
|
201
|
-
console.log(` ${C.GRAY}ββ API Key: ${C.RESET}${apiKey === "NOT_SET" ? C.RED : C.GREEN}${masked}${C.RESET}`);
|
|
202
|
-
|
|
203
260
|
if (apiKey !== "NOT_SET") {
|
|
204
261
|
try {
|
|
205
|
-
process.stdout.write(` ${C.GRAY}ββ Gateway: ${C.RESET}π‘ Pinging...`);
|
|
206
262
|
const client = new DeFlakeClient();
|
|
207
|
-
const start = Date.now();
|
|
208
263
|
const usage = await client.getUsage();
|
|
209
|
-
const latency = Date.now() - start;
|
|
210
264
|
if (usage?.status === 'success') {
|
|
211
|
-
|
|
212
|
-
console.log(` ${C.GRAY}ββ Quota: ${C.RESET}${C.MAGENTA}${usage.data.usage}/${usage.data.limit} fixes used${C.RESET}`);
|
|
265
|
+
console.log(` ${C.GRAY}ββ Connection: ${C.RESET}${C.GREEN}ONLINE${C.RESET}`);
|
|
266
|
+
console.log(` ${C.GRAY}ββ Quota: ${C.RESET}${C.MAGENTA}${usage.data.usage}/${usage.data.limit} monthly fixes used${C.RESET}`);
|
|
213
267
|
} else {
|
|
214
|
-
|
|
268
|
+
console.log(` ${C.GRAY}ββ Connection: ${C.RESET}${C.RED}OFFLINE (Unauthorized)${C.RESET}`);
|
|
215
269
|
}
|
|
216
270
|
} catch (e) {
|
|
217
|
-
|
|
271
|
+
console.log(` ${C.GRAY}ββ Connection: ${C.RESET}${C.RED}OFFLINE (${e.message})${C.RESET}`);
|
|
218
272
|
}
|
|
219
273
|
} else {
|
|
220
|
-
console.log(` ${C.GRAY}ββ
|
|
274
|
+
console.log(` ${C.GRAY}ββ API Key: ${C.RED}MISSING${C.RESET}`);
|
|
221
275
|
}
|
|
222
276
|
console.log("");
|
|
223
277
|
|
|
224
|
-
// 4.
|
|
225
|
-
console.log(`${C.CYAN}4.
|
|
278
|
+
// 4. DEEP ACCESS AUDIT (The Truth Teller)
|
|
279
|
+
console.log(`${C.CYAN}4. DEEP ACCESS AUDIT (The Truth Teller)${C.RESET}`);
|
|
226
280
|
const artDirs = [
|
|
227
281
|
{ path: 'test-results', label: 'Test Results' },
|
|
228
282
|
{ path: 'playwright-report', label: 'HTML Report' }
|
|
229
283
|
];
|
|
230
|
-
let
|
|
231
|
-
let
|
|
284
|
+
let allPristine = true;
|
|
285
|
+
let sandboxed = false;
|
|
232
286
|
|
|
233
287
|
for (const d of artDirs) {
|
|
234
288
|
if (fs.existsSync(d.path)) {
|
|
235
|
-
process.stdout.write(` ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}π
|
|
236
|
-
let dItems = 0;
|
|
289
|
+
process.stdout.write(` ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}π Scanning Deep...`);
|
|
237
290
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
process.stdout.write(`\r ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.GREEN}${C.BRIGHT}[LIBERADOS]${C.RESET}${C.GREEN} (${dItems} Γtems accesibles)${C.RESET} \n`);
|
|
291
|
+
// A shallow accessSync is NOT enough for Mac Sandbox. Must list and stat.
|
|
292
|
+
const items = fs.readdirSync(d.path);
|
|
293
|
+
if (items.length > 0) {
|
|
294
|
+
process.stdout.write(`\r ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.GREEN}${C.BRIGHT}[LIBERADOS]${C.RESET}${C.GREEN} (${items.length} top-level objects)${C.RESET} \n`);
|
|
295
|
+
} else {
|
|
296
|
+
process.stdout.write(`\r ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.GRAY}Vacia (No hay resultados)${C.RESET} \n`);
|
|
297
|
+
}
|
|
246
298
|
} catch (e) {
|
|
247
|
-
|
|
248
|
-
|
|
299
|
+
if (e.code === 'EPERM' || e.message.includes('Operation not permitted')) {
|
|
300
|
+
process.stdout.write(`\r ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.BG_RED}${C.WHITE} BLOQUEADO POR MAC PRIVACY ${C.RESET} \n`);
|
|
301
|
+
sandboxed = true;
|
|
302
|
+
} else {
|
|
303
|
+
process.stdout.write(`\r ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.RED}[ERROR] (${e.code})${C.RESET} \n`);
|
|
304
|
+
}
|
|
305
|
+
allPristine = false;
|
|
249
306
|
}
|
|
250
307
|
} else {
|
|
251
|
-
console.log(` ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.GRAY}
|
|
308
|
+
console.log(` ${C.GRAY}ββ ${d.label.padEnd(16)}: ${C.RESET}${C.GRAY}No detectada${C.RESET}`);
|
|
252
309
|
}
|
|
253
310
|
}
|
|
254
311
|
|
|
255
|
-
if (
|
|
256
|
-
console.log(`\n ${C.
|
|
312
|
+
if (sandboxed) {
|
|
313
|
+
console.log(`\n ${C.BG_RED}${C.WHITE}${C.BRIGHT} ALERTA: MAC SANDBOX DETECTADO ${C.RESET}`);
|
|
314
|
+
console.log(` ${C.BRIGHT}Tu proyecto estΓ‘ en Documents/Desktop y Mac bloquea el acceso profundo.${C.RESET}`);
|
|
315
|
+
console.log(` ${C.CYAN}SOLUCIΓN 1:${C.RESET} Dale "Full Disk Access" a tu Terminal en Settings > Privacy.`);
|
|
316
|
+
console.log(` ${C.CYAN}SOLUCIΓN 2:${C.RESET} Mueve el proyecto fuera de Documents (ej: /Users/hugo/code/...)`);
|
|
317
|
+
} else if (!allPristine) {
|
|
318
|
+
console.log(`\n ${C.YELLOW}β οΈ PERMISOS DE ARCHIVO REQUERIDOS:${C.RESET}`);
|
|
257
319
|
const fix = process.platform === 'win32' ? 'icacls . /grant ${env:USERNAME}:(OI)(CI)F /T' : 'sudo chown -R $(whoami) .';
|
|
258
|
-
console.log(` Ejecuta
|
|
320
|
+
console.log(` Ejecuta: ${C.CYAN}${fix}${C.RESET}`);
|
|
259
321
|
} else {
|
|
260
|
-
console.log(
|
|
261
|
-
console.log(`\n${C.BG_GREEN}${C.WHITE}${C.BRIGHT} ESTADO DEL SISTEMA: LIBERADO Y OPERATIVO ${C.RESET}`);
|
|
262
|
-
console.log(` Ya puedes correr: ${C.CYAN}npx deflake --fix --report npx playwright test${C.RESET}`);
|
|
322
|
+
console.log(`\n${C.BG_GREEN}${C.WHITE}${C.BRIGHT} SISTEMA SALUDABLE Y LIBERADO ${C.RESET}`);
|
|
263
323
|
}
|
|
264
|
-
console.log("\n" + C.GRAY + "β".repeat(
|
|
324
|
+
console.log("\n" + C.GRAY + "β".repeat(55) + C.RESET + "\n");
|
|
265
325
|
}
|
|
266
326
|
|
|
267
327
|
function showReport() {
|
|
328
|
+
// Kill any existing report server to prevent EADDRINUSE
|
|
329
|
+
try {
|
|
330
|
+
execSync('lsof -ti:9323 | xargs kill -9 2>/dev/null', { stdio: 'ignore' });
|
|
331
|
+
} catch (e) { /* no process to kill, that's fine */ }
|
|
268
332
|
spawn('npx playwright show-report', { shell: true, stdio: 'inherit' }).on('error', () => {});
|
|
269
333
|
}
|
package/client.js
CHANGED
|
@@ -105,11 +105,13 @@ class DeFlakeClient {
|
|
|
105
105
|
|
|
106
106
|
async heal(logPath, htmlPath, failureLocation = null, sourceCode = null, applyFix = false) {
|
|
107
107
|
try {
|
|
108
|
-
// ... (rest of the check logic) ...
|
|
109
|
-
if (!fs.existsSync(logPath)) throw new Error(`Log file not found: ${logPath}`);
|
|
110
108
|
if (!fs.existsSync(htmlPath)) throw new Error(`HTML file not found: ${htmlPath}`);
|
|
111
109
|
|
|
112
|
-
|
|
110
|
+
// logPath is optional β when null, we use htmlPath as the primary error context
|
|
111
|
+
let logContent = '';
|
|
112
|
+
if (logPath && fs.existsSync(logPath)) {
|
|
113
|
+
logContent = fs.readFileSync(logPath, 'utf8');
|
|
114
|
+
}
|
|
113
115
|
|
|
114
116
|
// Handle binary artifacts (Screenshots) vs Text artifacts (HTML/MD)
|
|
115
117
|
const isImage = htmlPath.toLowerCase().endsWith('.png');
|