deflake 1.2.12 → 1.2.13
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 +107 -89
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -427,99 +427,77 @@ function extractFailureLocation(logText) {
|
|
|
427
427
|
// --- COLORS Moved to top ---
|
|
428
428
|
|
|
429
429
|
function printDetailedFix(fixText, location, sourceCode = null, isApplied = false) {
|
|
430
|
-
|
|
431
|
-
let fixCode = fixText;
|
|
430
|
+
let patches = [];
|
|
432
431
|
let explanation = null;
|
|
433
432
|
|
|
434
433
|
try {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
434
|
+
// Strip markdown if present
|
|
435
|
+
const cleaned = fixText.replace(/```json\n?/, '').replace(/```/, '').trim();
|
|
436
|
+
const parsed = JSON.parse(cleaned);
|
|
437
|
+
explanation = parsed.explanation || parsed.reason;
|
|
438
|
+
if (parsed.patches && Array.isArray(parsed.patches)) {
|
|
439
|
+
patches = parsed.patches;
|
|
440
|
+
} else if (parsed.code) {
|
|
441
|
+
patches = [{
|
|
442
|
+
file: location ? (location.specFile || location.rootFile) : 'unknown',
|
|
443
|
+
line: location ? (location.testLine || location.rootLine) : 0,
|
|
444
|
+
action: 'REPLACE',
|
|
445
|
+
new_line: parsed.code
|
|
446
|
+
}];
|
|
447
|
+
}
|
|
448
|
+
} catch (e) {
|
|
449
|
+
// If it looks like JSON but couldn't be parsed, try harder or fallback
|
|
450
|
+
if (fixText.trim().startsWith('{')) {
|
|
451
|
+
try {
|
|
452
|
+
const match = fixText.match(/\{[\s\S]*\}/);
|
|
453
|
+
if (match) {
|
|
454
|
+
const parsed = JSON.parse(match[0]);
|
|
455
|
+
explanation = parsed.explanation || parsed.reason;
|
|
456
|
+
patches = parsed.patches || (parsed.code ? [{ file: 'target', line: 0, action: 'REPLACE', new_line: parsed.code }] : []);
|
|
457
|
+
}
|
|
458
|
+
} catch (e2) {}
|
|
439
459
|
}
|
|
440
|
-
|
|
460
|
+
|
|
461
|
+
if (patches.length === 0) {
|
|
462
|
+
patches = [{
|
|
463
|
+
file: location ? (location.specFile || location.rootFile) : 'unknown',
|
|
464
|
+
line: location ? (location.testLine || location.rootLine) : 0,
|
|
465
|
+
action: 'REPLACE',
|
|
466
|
+
new_line: fixText
|
|
467
|
+
}];
|
|
468
|
+
}
|
|
469
|
+
}
|
|
441
470
|
|
|
442
471
|
console.log("\n" + C.GRAY + "─".repeat(50) + C.RESET);
|
|
443
472
|
|
|
444
473
|
if (isApplied) {
|
|
445
|
-
// --- APPLIED VIEW ---
|
|
446
474
|
console.log(`${C.GREEN}${C.BRIGHT}✅ FIX APPLIED:${C.RESET}`);
|
|
447
475
|
if (location) {
|
|
448
|
-
// Label is default color, Value is colored
|
|
449
476
|
const fileLabel = location.specFile || location.rootFile;
|
|
450
477
|
let lineLabel = location.testLine || location.rootLine;
|
|
451
|
-
|
|
452
478
|
if (fileLabel) {
|
|
453
479
|
console.log(`${C.BRIGHT}📄 File:${C.RESET} ${fileLabel}:${lineLabel}`);
|
|
454
480
|
}
|
|
455
481
|
}
|
|
456
482
|
|
|
457
|
-
if (explanation) {
|
|
483
|
+
if (explanation) {
|
|
458
484
|
console.log(`\n${C.BRIGHT}ℹ️ Reason:${C.RESET} ${explanation}`);
|
|
459
485
|
}
|
|
460
486
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
console.log(`${C.RED}🔴 OLD:${C.RESET}`);
|
|
466
|
-
try {
|
|
467
|
-
const lines = sourceCode.split('\n');
|
|
468
|
-
const centerIdx = parseInt(location.rootLine) - 1;
|
|
469
|
-
// Show a small window around the error
|
|
470
|
-
const start = Math.max(0, centerIdx - 2);
|
|
471
|
-
const end = Math.min(lines.length - 1, centerIdx + 2);
|
|
472
|
-
|
|
473
|
-
for (let i = start; i <= end; i++) {
|
|
474
|
-
let prefix = (i === centerIdx) ? "> " : " ";
|
|
475
|
-
let line = lines[i];
|
|
476
|
-
if (i === centerIdx) line = `${C.RED}${line}${C.RESET}`;
|
|
477
|
-
else line = `${C.GRAY}${line}${C.RESET}`;
|
|
478
|
-
console.log(prefix + line);
|
|
479
|
-
}
|
|
480
|
-
} catch (e) { }
|
|
487
|
+
for (const patch of patches) {
|
|
488
|
+
console.log(C.GRAY + "─".repeat(20) + C.RESET);
|
|
489
|
+
console.log(`${C.BRIGHT}📄 File:${C.RESET} ${path.basename(patch.file)}:${patch.line} [${patch.action}]`);
|
|
490
|
+
console.log(`\n${C.GREEN}${C.BRIGHT}🟢 NEW:${C.RESET}\n ${C.GREEN}${patch.new_line.trim()}${C.RESET}`);
|
|
481
491
|
}
|
|
482
|
-
|
|
483
|
-
// --- NEW CODE ---
|
|
484
|
-
console.log(`\n${C.GREEN}🟢 NEW:${C.RESET}`);
|
|
485
|
-
fixCode.split('\n').forEach(line => {
|
|
486
|
-
let colored = line
|
|
487
|
-
.replace(/(\/\/.*)/g, `${C.GRAY}$1${C.RESET}`)
|
|
488
|
-
.replace(/\b(const|let|var|await|async|function|return)\b/g, `${C.YELLOW}$1${C.RESET}`)
|
|
489
|
-
.replace(/('.*?')|(".*?")|(`.*?`)/g, `${C.GREEN}$1${C.RESET}`)
|
|
490
|
-
.replace(/(\.click|\.fill|\.locator)/g, `${C.CYAN}$1${C.RESET}`);
|
|
491
|
-
console.log(" " + colored);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
492
|
} else {
|
|
495
|
-
|
|
496
|
-
if (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
let targetLabel = location.specFile;
|
|
502
|
-
if (location.testLine) targetLabel += `:${location.testLine}`;
|
|
503
|
-
console.log(`🎯 Fix Target: ${C.CYAN}${targetLabel} (Definition)${C.RESET}`);
|
|
504
|
-
}
|
|
493
|
+
console.log(`${C.CYAN}${C.BRIGHT}💡 SUGGESTED FIX:${C.RESET}`);
|
|
494
|
+
if (explanation) {
|
|
495
|
+
console.log(`\n${C.BRIGHT}ℹ️ Reason:${C.RESET} ${explanation}`);
|
|
496
|
+
}
|
|
497
|
+
for (const patch of patches) {
|
|
498
|
+
console.log(` - ${C.CYAN}${path.basename(patch.file)}:${patch.line}${C.RESET}: ${patch.new_line.trim()}`);
|
|
505
499
|
}
|
|
506
|
-
console.log(C.GRAY + "─".repeat(50) + C.RESET);
|
|
507
|
-
|
|
508
|
-
console.log(`${C.GREEN}${C.BRIGHT}✨ DEFLAKE SUGGESTION:${C.RESET}`);
|
|
509
|
-
if (explanation) console.log(`${C.GRAY}// ${explanation}${C.RESET}`);
|
|
510
|
-
|
|
511
|
-
// Print code with simple coloring
|
|
512
|
-
fixCode.split('\n').forEach(line => {
|
|
513
|
-
let colored = line
|
|
514
|
-
.replace(/(\/\/.*)/g, `${C.GRAY}$1${C.RESET}`)
|
|
515
|
-
.replace(/\b(const|let|var|await|async|function|return)\b/g, `${C.YELLOW}$1${C.RESET}`)
|
|
516
|
-
.replace(/('.*?')|(".*?")|(`.*?`)/g, `${C.GREEN}$1${C.RESET}`)
|
|
517
|
-
.replace(/(\.click|\.fill|\.locator)/g, `${C.CYAN}$1${C.RESET}`);
|
|
518
|
-
console.log(" " + colored);
|
|
519
|
-
});
|
|
520
500
|
}
|
|
521
|
-
|
|
522
|
-
console.log(C.GRAY + "─".repeat(50) + C.RESET);
|
|
523
501
|
}
|
|
524
502
|
|
|
525
503
|
/**
|
|
@@ -749,34 +727,74 @@ async function applySelfHealing(result) {
|
|
|
749
727
|
}];
|
|
750
728
|
}
|
|
751
729
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
730
|
+
const backups = new Map();
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
for (const patch of patches) {
|
|
734
|
+
const pFile = patch.file || filePath;
|
|
735
|
+
const pLine = parseInt(patch.line || targetLine);
|
|
736
|
+
const pAction = patch.action || 'REPLACE';
|
|
737
|
+
let pNew = patch.new_line;
|
|
757
738
|
|
|
758
|
-
|
|
739
|
+
if (!fs.existsSync(pFile)) continue;
|
|
759
740
|
|
|
760
|
-
|
|
761
|
-
|
|
741
|
+
// Create backup if not already done
|
|
742
|
+
if (!backups.has(pFile)) {
|
|
743
|
+
backups.set(pFile, fs.readFileSync(pFile, 'utf8'));
|
|
744
|
+
}
|
|
762
745
|
|
|
763
|
-
|
|
746
|
+
const currentLines = fs.readFileSync(pFile, 'utf8').split('\n');
|
|
747
|
+
const originalLineIndex = pLine - 1;
|
|
764
748
|
|
|
765
|
-
|
|
766
|
-
const indentation = originalLine.match(/^\s*/)[0];
|
|
749
|
+
if (originalLineIndex < 0 || originalLineIndex >= currentLines.length) continue;
|
|
767
750
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
751
|
+
const originalLine = currentLines[originalLineIndex];
|
|
752
|
+
const indentation = originalLine.match(/^\s*/)[0];
|
|
753
|
+
|
|
754
|
+
// CRITICAL: Prevent injecting JSON metadata into the code
|
|
755
|
+
if (pNew.trim().startsWith('{') && pNew.includes('"patches"') && pNew.includes(':')) {
|
|
756
|
+
console.log(` ❌ ${C.RED}[Self-Healing] Detected attempted JSON injection. Aborting patch for ${path.basename(pFile)}.${C.RESET}`);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const finalNewLine = indentation + pNew.trim();
|
|
761
|
+
|
|
762
|
+
// DUPLICATE PROTECTION: Don't insert/replace if code already exists
|
|
763
|
+
if (currentLines.some(l => l.trim() === pNew.trim())) {
|
|
764
|
+
console.log(` ℹ️ ${C.GRAY}[Self-Healing] Skipping duplicate patch for:${C.RESET} ${path.basename(pFile)}`);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (pAction === 'INSERT_AFTER') {
|
|
769
|
+
currentLines.splice(pLine, 0, finalNewLine);
|
|
770
|
+
fs.writeFileSync(pFile, currentLines.join('\n'));
|
|
771
|
+
console.log(` ✅ ${C.GREEN}[Self-Healing] Successfully inserted at:${C.RESET} ${path.basename(pFile)}:${pLine}`);
|
|
772
|
+
} else {
|
|
773
|
+
currentLines[originalLineIndex] = finalNewLine;
|
|
774
|
+
fs.writeFileSync(pFile, currentLines.join('\n'));
|
|
775
|
+
console.log(` ✅ ${C.GREEN}[Self-Healing] Successfully patched:${C.RESET} ${path.basename(pFile)}:${pLine}`);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// SYNTAX VALIDATION: Rollback if syntax is broken
|
|
779
|
+
if (pFile.endsWith('.ts') || pFile.endsWith('.js')) {
|
|
780
|
+
try {
|
|
781
|
+
const { execSync } = require('child_process');
|
|
782
|
+
execSync(`node --check "${pFile}"`, { stdio: 'ignore' });
|
|
783
|
+
} catch (err) {
|
|
784
|
+
console.log(` ⚠️ ${C.YELLOW}[Self-Healing] Patch broke syntax in ${path.basename(pFile)}. Rolling back...${C.RESET}`);
|
|
785
|
+
fs.writeFileSync(pFile, backups.get(pFile));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
776
788
|
}
|
|
789
|
+
} catch (patchError) {
|
|
790
|
+
console.error(` ❌ ${C.RED}[Self-Healing] Error applying patches: ${patchError.message}${C.RESET}`);
|
|
791
|
+
// Emergency rollback for all involved files
|
|
792
|
+
for (const [file, content] of backups) {
|
|
793
|
+
fs.writeFileSync(file, content);
|
|
794
|
+
}
|
|
777
795
|
}
|
|
778
796
|
} catch (error) {
|
|
779
|
-
console.error(` ❌ ${C.RED}[Self-Healing] Error
|
|
797
|
+
console.error(` ❌ ${C.RED}[Self-Healing] Error in self-healing logic:${C.RESET} ${error.message}`);
|
|
780
798
|
}
|
|
781
799
|
}
|
|
782
800
|
|