deflake 1.2.12 → 1.2.15
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 +144 -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
|
/**
|
|
@@ -703,6 +681,43 @@ async function runDoctor(argv) {
|
|
|
703
681
|
} catch (error) {
|
|
704
682
|
process.stdout.write(`\r ❌ ${C.RED}API Connectivity Error: ${error.message}${C.RESET}\n`);
|
|
705
683
|
}
|
|
684
|
+
// 6. Artifact & Permission Check
|
|
685
|
+
console.log(`${C.BRIGHT}Checking Artifact Permissions:${C.RESET}`);
|
|
686
|
+
const criticalDirs = [
|
|
687
|
+
{ path: 'test-results', label: 'Playwright Failures' },
|
|
688
|
+
{ path: 'playwright-report', label: 'Playwright Report' },
|
|
689
|
+
{ path: 'cypress/screenshots', label: 'Cypress Screenshots' },
|
|
690
|
+
{ path: 'cypress/videos', label: 'Cypress Videos' }
|
|
691
|
+
];
|
|
692
|
+
|
|
693
|
+
let permsIssue = false;
|
|
694
|
+
for (const dir of criticalDirs) {
|
|
695
|
+
if (fs.existsSync(dir.path)) {
|
|
696
|
+
try {
|
|
697
|
+
fs.accessSync(dir.path, fs.constants.R_OK | fs.constants.W_OK);
|
|
698
|
+
// Deep check: try readdir
|
|
699
|
+
fs.readdirSync(dir.path);
|
|
700
|
+
console.log(` ✅ ${dir.label.padEnd(20)}: ${C.GREEN}Accessible${C.RESET}`);
|
|
701
|
+
} catch (e) {
|
|
702
|
+
console.log(` ❌ ${dir.label.padEnd(20)}: ${C.RED}Access Denied${C.RESET}`);
|
|
703
|
+
console.log(` ${C.GRAY}Path: ${dir.path}${C.RESET}`);
|
|
704
|
+
permsIssue = true;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (permsIssue) {
|
|
710
|
+
console.log(`\n ${C.YELLOW}⚠️ Permission bottleneck detected!${C.RESET}`);
|
|
711
|
+
if (process.platform === 'win32') {
|
|
712
|
+
console.log(` ${C.BRIGHT}Windows Fix:${C.RESET} Run this in PowerShell as Admin:`);
|
|
713
|
+
console.log(` ${C.CYAN}icacls . /grant \${env:USERNAME}:(OI)(CI)F /T${C.RESET}`);
|
|
714
|
+
} else {
|
|
715
|
+
console.log(` ${C.BRIGHT}macOS/Linux Fix:${C.RESET} Run this command:`);
|
|
716
|
+
console.log(` ${C.CYAN}sudo chown -R $(whoami) .${C.RESET}`);
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
console.log(` ✅ All critical directories are accessible`);
|
|
720
|
+
}
|
|
706
721
|
console.log("");
|
|
707
722
|
|
|
708
723
|
console.log(`${C.BRIGHT}Summary:${C.RESET}`);
|
|
@@ -749,34 +764,74 @@ async function applySelfHealing(result) {
|
|
|
749
764
|
}];
|
|
750
765
|
}
|
|
751
766
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
767
|
+
const backups = new Map();
|
|
768
|
+
|
|
769
|
+
try {
|
|
770
|
+
for (const patch of patches) {
|
|
771
|
+
const pFile = patch.file || filePath;
|
|
772
|
+
const pLine = parseInt(patch.line || targetLine);
|
|
773
|
+
const pAction = patch.action || 'REPLACE';
|
|
774
|
+
let pNew = patch.new_line;
|
|
757
775
|
|
|
758
|
-
|
|
776
|
+
if (!fs.existsSync(pFile)) continue;
|
|
759
777
|
|
|
760
|
-
|
|
761
|
-
|
|
778
|
+
// Create backup if not already done
|
|
779
|
+
if (!backups.has(pFile)) {
|
|
780
|
+
backups.set(pFile, fs.readFileSync(pFile, 'utf8'));
|
|
781
|
+
}
|
|
762
782
|
|
|
763
|
-
|
|
783
|
+
const currentLines = fs.readFileSync(pFile, 'utf8').split('\n');
|
|
784
|
+
const originalLineIndex = pLine - 1;
|
|
764
785
|
|
|
765
|
-
|
|
766
|
-
const indentation = originalLine.match(/^\s*/)[0];
|
|
786
|
+
if (originalLineIndex < 0 || originalLineIndex >= currentLines.length) continue;
|
|
767
787
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
788
|
+
const originalLine = currentLines[originalLineIndex];
|
|
789
|
+
const indentation = originalLine.match(/^\s*/)[0];
|
|
790
|
+
|
|
791
|
+
// CRITICAL: Prevent injecting JSON metadata into the code
|
|
792
|
+
if (pNew.trim().startsWith('{') && pNew.includes('"patches"') && pNew.includes(':')) {
|
|
793
|
+
console.log(` ❌ ${C.RED}[Self-Healing] Detected attempted JSON injection. Aborting patch for ${path.basename(pFile)}.${C.RESET}`);
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const finalNewLine = indentation + pNew.trim();
|
|
798
|
+
|
|
799
|
+
// DUPLICATE PROTECTION: Don't insert/replace if code already exists
|
|
800
|
+
if (currentLines.some(l => l.trim() === pNew.trim())) {
|
|
801
|
+
console.log(` ℹ️ ${C.GRAY}[Self-Healing] Skipping duplicate patch for:${C.RESET} ${path.basename(pFile)}`);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (pAction === 'INSERT_AFTER') {
|
|
806
|
+
currentLines.splice(pLine, 0, finalNewLine);
|
|
807
|
+
fs.writeFileSync(pFile, currentLines.join('\n'));
|
|
808
|
+
console.log(` ✅ ${C.GREEN}[Self-Healing] Successfully inserted at:${C.RESET} ${path.basename(pFile)}:${pLine}`);
|
|
809
|
+
} else {
|
|
810
|
+
currentLines[originalLineIndex] = finalNewLine;
|
|
811
|
+
fs.writeFileSync(pFile, currentLines.join('\n'));
|
|
812
|
+
console.log(` ✅ ${C.GREEN}[Self-Healing] Successfully patched:${C.RESET} ${path.basename(pFile)}:${pLine}`);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// SYNTAX VALIDATION: Rollback if syntax is broken
|
|
816
|
+
if (pFile.endsWith('.ts') || pFile.endsWith('.js')) {
|
|
817
|
+
try {
|
|
818
|
+
const { execSync } = require('child_process');
|
|
819
|
+
execSync(`node --check "${pFile}"`, { stdio: 'ignore' });
|
|
820
|
+
} catch (err) {
|
|
821
|
+
console.log(` ⚠️ ${C.YELLOW}[Self-Healing] Patch broke syntax in ${path.basename(pFile)}. Rolling back...${C.RESET}`);
|
|
822
|
+
fs.writeFileSync(pFile, backups.get(pFile));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
776
825
|
}
|
|
826
|
+
} catch (patchError) {
|
|
827
|
+
console.error(` ❌ ${C.RED}[Self-Healing] Error applying patches: ${patchError.message}${C.RESET}`);
|
|
828
|
+
// Emergency rollback for all involved files
|
|
829
|
+
for (const [file, content] of backups) {
|
|
830
|
+
fs.writeFileSync(file, content);
|
|
831
|
+
}
|
|
777
832
|
}
|
|
778
833
|
} catch (error) {
|
|
779
|
-
console.error(` ❌ ${C.RED}[Self-Healing] Error
|
|
834
|
+
console.error(` ❌ ${C.RED}[Self-Healing] Error in self-healing logic:${C.RESET} ${error.message}`);
|
|
780
835
|
}
|
|
781
836
|
}
|
|
782
837
|
|