UNPKG

testeranto

Version:

the AI powered BDD test framework for typescript projects

227 lines (226 loc) 10 kB
/* eslint-disable @typescript-eslint/no-unused-vars */ import path from "path"; import fs from "fs"; import chokidar from "chokidar"; import { generateGolingvuMetafile, writeGolingvuMetafile, } from "./golingvuMetafile"; export class GolingvuWatcher { constructor(testName, entryPoints) { this.watcher = null; this.onChangeCallback = null; this.debounceTimer = null; this.testName = testName; this.entryPoints = entryPoints; } async start() { // Watch source Go files to trigger metafile regeneration const goFilesPattern = "**/*.go"; this.watcher = chokidar.watch(goFilesPattern, { persistent: true, ignoreInitial: true, cwd: process.cwd(), ignored: [ "**/node_modules/**", "**/.git/**", "**/testeranto/bundles/**", "**/testeranto/reports/**", ], usePolling: true, interval: 1000, binaryInterval: 1000, depth: 99, followSymlinks: false, atomic: false, }); // Add event listeners for source file changes with more detailed logging this.watcher .on("add", (filePath) => { console.log(`File added: ${filePath}`); this.handleFileChange("add", filePath); }) .on("change", (filePath) => { console.log(`File changed: ${filePath}`); this.handleFileChange("change", filePath); }) .on("unlink", (filePath) => { console.log(`File removed: ${filePath}`); this.handleFileChange("unlink", filePath); }) .on("addDir", (dirPath) => { console.log(`Directory added: ${dirPath}`); }) .on("unlinkDir", (dirPath) => { console.log(`Directory removed: ${dirPath}`); }) .on("error", (error) => { console.error(`Source watcher error: ${error}`); }) .on("ready", () => { var _a; console.log("Initial golang source file scan complete. Ready for changes."); const watched = (_a = this.watcher) === null || _a === void 0 ? void 0 : _a.getWatched(); // If no directories are being watched, let's try a different approach if (Object.keys(watched || {}).length === 0) { console.error("WARNING: No directories are being watched!"); console.error("Trying to manually find and watch .go files..."); // Manually find all .go files and add them to the watcher const findAllGoFiles = (dir) => { let results = []; const list = fs.readdirSync(dir); list.forEach((file) => { const filePath = path.join(dir, "example", file); const stat = fs.statSync(filePath); if (stat.isDirectory()) { // Skip ignored directories if (file === "node_modules" || file === ".git" || file === "testeranto") { return; } results = results.concat(findAllGoFiles(filePath)); } else if (file.endsWith(".go")) { results.push(filePath); } }); return results; }; // try { // const allGoFiles = findAllGoFiles(process.cwd()); // console.log(`Found ${allGoFiles.length} Go files manually:`); // allGoFiles.forEach((file) => console.log(` ${file}`)); // // Add these files to the watcher // allGoFiles.forEach((file) => { // this.watcher?.add(file); // }); // } catch (error) { // console.error("Error manually finding Go files:", error); // } } else { // // Log each directory and its files // for (const [dir, files] of Object.entries(watched || {})) { // console.log(`Directory: ${dir}`); // console.log(`Files: ${(files as string[]).join(", ")}`); // } } }) .on("raw", (event, path, details) => { // This can help debug what events are being emitted // console.log(`Raw event: ${event} on path: ${path}`); }); // Second watcher: watches bundle files in the core directory const outputDir = path.join(process.cwd(), "testeranto", "bundles", "golang", "core"); // Ensure the output directory exists // if (!fs.existsSync(outputDir)) { // fs.mkdirSync(outputDir, { recursive: true }); // } // console.log(`Watching bundle directory: ${outputDir}`); // Track the last seen signatures to detect changes const lastSignatures = new Map(); // Create a separate watcher for bundle files, including .golingvu.go files const bundleWatcher = chokidar.watch([path.join(outputDir, "*.go"), path.join(outputDir, "*.golingvu.go")], { persistent: true, ignoreInitial: false, // We want to capture initial files to establish baseline awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50, }, }); bundleWatcher .on("add", (filePath) => { this.readAndCheckSignature(filePath, lastSignatures); }) .on("change", (filePath) => { this.readAndCheckSignature(filePath, lastSignatures); }) .on("error", (error) => console.error(`Bundle watcher error: ${error}`)); // Initial metafile generation await this.regenerateMetafile(); } async handleFileChange(event, filePath) { // Debounce file changes to prevent multiple rapid triggers // Use a simple timeout-based debounce if (this.debounceTimer) { clearTimeout(this.debounceTimer); } this.debounceTimer = setTimeout(async () => { const fullPath = path.join(process.cwd(), filePath); // Add a small delay to ensure the file is fully written await new Promise((resolve) => setTimeout(resolve, 100)); // Check if the file exists and log its stats // if (fs.existsSync(fullPath)) { // try { // const stats = fs.statSync(fullPath); // console.log(`File ${filePath} changed (${stats.size} bytes)`); // } catch (error) { // console.error(`Error reading file: ${error}`); // } // } console.log("Regenerating metafile due to file change..."); await this.regenerateMetafile(); if (this.onChangeCallback) { this.onChangeCallback(); } }, 300); // 300ms debounce } readAndCheckSignature(filePath, lastSignatures) { try { const content = fs.readFileSync(filePath, "utf-8"); // Extract the signature from the comment const signatureMatch = content.match(/\/\/ Signature: (\w+)/); if (signatureMatch && signatureMatch[1]) { const currentSignature = signatureMatch[1]; const lastSignature = lastSignatures.get(filePath); if (lastSignature === undefined) { // First time seeing this file, just record the signature lastSignatures.set(filePath, currentSignature); } else if (lastSignature !== currentSignature) { // Signature changed, trigger test scheduling lastSignatures.set(filePath, currentSignature); // Find which entry point this corresponds to // Handle .golingvu.go files by mapping back to the original .go file const fileName = path.basename(filePath); // Remove .golingvu from the filename to find the original entry point const originalFileName = fileName.replace(".golingvu.go", ".go"); const originalEntryPoint = this.entryPoints.find((ep) => path.basename(ep) === originalFileName); if (originalEntryPoint) { // Add to processing queue if (this.onChangeCallback) { this.onChangeCallback(); } } } } } catch (error) { console.error(`Error reading bundle file ${filePath}:`, error); } } async regenerateMetafile() { console.log("regenerateMetafile!"); try { // console.log("Regenerating golingvu metafile..."); // Always regenerate using the original entry points const metafile = await generateGolingvuMetafile(this.testName, this.entryPoints); writeGolingvuMetafile(this.testName, metafile); // console.log("Golingvu metafile regenerated due to Go file changes"); } catch (error) { console.error("Error regenerating golingvu metafile:", error); } } onMetafileChange(callback) { this.onChangeCallback = callback; } stop() { if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = null; } if (this.watcher) { this.watcher.close(); this.watcher = null; } } }