@masuidrive/ticket
Version:
Real-time ticket tracking viewer with Vite + Express
80 lines (68 loc) • 2.08 kB
text/typescript
import { watch } from 'chokidar';
import { EventEmitter } from 'events';
import path from 'path';
import { createHash } from 'crypto';
import { readFile } from 'fs/promises';
export class FileWatcher extends EventEmitter {
private watcher: any;
private projectRoot: string;
private fileHashes: Map<string, string> = new Map();
constructor(projectRoot?: string) {
super();
this.projectRoot = projectRoot || process.cwd();
}
private async getFileHash(filePath: string): Promise<string | null> {
try {
const content = await readFile(filePath, 'utf-8');
return createHash('sha256').update(content).digest('hex');
} catch (error) {
return null;
}
}
watchFile(filePath: string = 'current-ticket.md'): void {
const fullPath = path.join(this.projectRoot, filePath);
// Get initial hash
this.getFileHash(fullPath).then(hash => {
if (hash) {
this.fileHashes.set(filePath, hash);
}
});
this.watcher = watch(fullPath, {
persistent: true,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 500,
pollInterval: 100
}
});
this.watcher.on('change', async () => {
// Check if content actually changed using SHA256
const newHash = await this.getFileHash(fullPath);
const oldHash = this.fileHashes.get(filePath);
if (newHash && newHash !== oldHash) {
this.fileHashes.set(filePath, newHash);
this.emit('fileChanged', filePath);
}
});
this.watcher.on('add', async () => {
const hash = await this.getFileHash(fullPath);
if (hash) {
this.fileHashes.set(filePath, hash);
}
this.emit('fileAdded', filePath);
});
this.watcher.on('unlink', () => {
this.fileHashes.delete(filePath);
this.emit('fileRemoved', filePath);
});
this.watcher.on('error', (error: Error) => {
this.emit('error', error);
});
}
stop(): void {
if (this.watcher) {
this.watcher.close();
}
this.fileHashes.clear();
}
}