or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

directory-operations.mdfile-descriptors.mdfile-operations.mdfile-watching.mdindex.mdstats-permissions.mdstreams.mdsymbolic-links.md

file-watching.mddocs/

0

# File Watching

1

2

File system monitoring capabilities with event-driven notifications for file and directory changes, supporting recursive watching and various encoding options. Provides real-time monitoring of filesystem changes.

3

4

## Capabilities

5

6

### File System Watching

7

8

Monitor files and directories for changes with event-based notifications.

9

10

```javascript { .api }

11

/**

12

* Watch for changes on a file or directory

13

* @param filename - File or directory path to watch

14

* @param options - Watch options including encoding and recursion

15

* @param listener - Event listener function (optional)

16

* @returns FSWatcher instance

17

*/

18

watch(filename: string | Buffer, options?: WatchOptions, listener?: WatchListener): FSWatcher;

19

watch(filename: string | Buffer, listener: WatchListener): FSWatcher;

20

watch(filename: string | Buffer, encoding: string, listener: WatchListener): FSWatcher;

21

22

interface WatchOptions {

23

/** Encoding for filename in events */

24

encoding?: string;

25

/** Keep process alive while watching */

26

persistent?: boolean;

27

/** Watch subdirectories recursively */

28

recursive?: boolean;

29

}

30

31

type WatchListener = (eventType: 'rename' | 'change', filename?: string | Buffer) => void;

32

33

interface FSWatcher extends EventEmitter {

34

/** Close the watcher and stop monitoring */

35

close(): void;

36

37

// Event emitters

38

on(event: 'change', listener: WatchListener): this;

39

on(event: 'error', listener: (error: Error) => void): this;

40

on(event: 'close', listener: () => void): this;

41

}

42

```

43

44

**Event Types:**

45

- `'change'` - File content was modified

46

- `'rename'` - File was created, deleted, or renamed

47

48

**Usage Examples:**

49

50

```javascript

51

// Basic file watching

52

const watcher = fs.watch('/important.txt', (eventType, filename) => {

53

console.log(`Event: ${eventType} on ${filename}`);

54

});

55

56

// Watch with options

57

const dirWatcher = fs.watch('/project', {

58

recursive: true,

59

encoding: 'utf8'

60

}, (eventType, filename) => {

61

console.log(`${eventType}: ${filename}`);

62

});

63

64

// Watch with event listeners

65

const fileWatcher = fs.watch('/config.json');

66

fileWatcher.on('change', (eventType, filename) => {

67

if (eventType === 'change') {

68

console.log('File content changed:', filename);

69

// Reload configuration

70

reloadConfig();

71

} else if (eventType === 'rename') {

72

console.log('File was renamed or deleted:', filename);

73

}

74

});

75

76

fileWatcher.on('error', (err) => {

77

console.error('Watcher error:', err);

78

});

79

80

fileWatcher.on('close', () => {

81

console.log('Watcher closed');

82

});

83

84

// Close watcher when done

85

setTimeout(() => {

86

fileWatcher.close();

87

}, 10000);

88

```

89

90

### Directory Watching

91

92

Monitor directories for file and subdirectory changes.

93

94

```javascript

95

// Watch directory for file changes

96

const dirWatcher = fs.watch('/uploads', (eventType, filename) => {

97

if (eventType === 'rename') {

98

if (fs.existsSync(`/uploads/${filename}`)) {

99

console.log('New file added:', filename);

100

} else {

101

console.log('File removed:', filename);

102

}

103

} else if (eventType === 'change') {

104

console.log('File modified:', filename);

105

}

106

});

107

108

// Recursive directory watching

109

const projectWatcher = fs.watch('/project', { recursive: true }, (eventType, filename) => {

110

console.log(`Project change - ${eventType}: ${filename}`);

111

112

// Handle different file types

113

if (filename.endsWith('.js')) {

114

console.log('JavaScript file changed, may need restart');

115

} else if (filename.endsWith('.json')) {

116

console.log('Config file changed, reloading...');

117

}

118

});

119

120

// Watch multiple directories

121

function watchMultiple(paths) {

122

const watchers = [];

123

124

paths.forEach(path => {

125

const watcher = fs.watch(path, { recursive: true }, (eventType, filename) => {

126

console.log(`[${path}] ${eventType}: ${filename}`);

127

});

128

watchers.push(watcher);

129

});

130

131

// Return cleanup function

132

return () => {

133

watchers.forEach(watcher => watcher.close());

134

};

135

}

136

137

const cleanup = watchMultiple(['/src', '/config', '/public']);

138

```

139

140

### Event Handling Patterns

141

142

Advanced patterns for handling file system events.

143

144

```javascript

145

// Debounced file watching (avoid multiple rapid events)

146

function createDebouncedWatcher(path, delay = 100) {

147

const timers = new Map();

148

149

return fs.watch(path, { recursive: true }, (eventType, filename) => {

150

const key = `${eventType}:${filename}`;

151

152

// Clear existing timer

153

if (timers.has(key)) {

154

clearTimeout(timers.get(key));

155

}

156

157

// Set new timer

158

const timer = setTimeout(() => {

159

console.log(`Debounced event - ${eventType}: ${filename}`);

160

timers.delete(key);

161

// Handle the event here

162

handleFileChange(eventType, filename);

163

}, delay);

164

165

timers.set(key, timer);

166

});

167

}

168

169

// Filter events by file type

170

function createFilteredWatcher(path, extensions) {

171

return fs.watch(path, { recursive: true }, (eventType, filename) => {

172

if (!filename) return;

173

174

const ext = filename.split('.').pop()?.toLowerCase();

175

if (extensions.includes(ext)) {

176

console.log(`${eventType} on ${ext} file: ${filename}`);

177

processFileChange(eventType, filename);

178

}

179

});

180

}

181

182

// Usage: only watch JavaScript and TypeScript files

183

const codeWatcher = createFilteredWatcher('/src', ['js', 'ts', 'jsx', 'tsx']);

184

185

// Event aggregation

186

function createAggregatingWatcher(path, flushInterval = 1000) {

187

const events = [];

188

189

const watcher = fs.watch(path, { recursive: true }, (eventType, filename) => {

190

events.push({ eventType, filename, timestamp: Date.now() });

191

});

192

193

// Flush events periodically

194

const interval = setInterval(() => {

195

if (events.length > 0) {

196

console.log(`Processing ${events.length} events:`);

197

events.forEach(event => {

198

console.log(` ${event.eventType}: ${event.filename}`);

199

});

200

201

processBatchedEvents([...events]);

202

events.length = 0; // Clear events

203

}

204

}, flushInterval);

205

206

// Return watcher with custom close method

207

const originalClose = watcher.close.bind(watcher);

208

watcher.close = () => {

209

clearInterval(interval);

210

originalClose();

211

};

212

213

return watcher;

214

}

215

```

216

217

### Configuration File Watching

218

219

Monitor configuration files and reload application state.

220

221

```javascript

222

// Configuration file watcher with reload

223

class ConfigWatcher {

224

constructor(configPath, onReload) {

225

this.configPath = configPath;

226

this.onReload = onReload;

227

this.config = this.loadConfig();

228

this.watcher = null;

229

this.startWatching();

230

}

231

232

loadConfig() {

233

try {

234

const content = fs.readFileSync(this.configPath, 'utf8');

235

return JSON.parse(content);

236

} catch (err) {

237

console.error('Error loading config:', err);

238

return {};

239

}

240

}

241

242

startWatching() {

243

this.watcher = fs.watch(this.configPath, (eventType) => {

244

if (eventType === 'change') {

245

console.log('Config file changed, reloading...');

246

const newConfig = this.loadConfig();

247

248

if (JSON.stringify(newConfig) !== JSON.stringify(this.config)) {

249

this.config = newConfig;

250

this.onReload(newConfig);

251

}

252

}

253

});

254

255

this.watcher.on('error', (err) => {

256

console.error('Config watcher error:', err);

257

});

258

}

259

260

stop() {

261

if (this.watcher) {

262

this.watcher.close();

263

this.watcher = null;

264

}

265

}

266

267

getConfig() {

268

return this.config;

269

}

270

}

271

272

// Usage

273

const configWatcher = new ConfigWatcher('/app/config.json', (newConfig) => {

274

console.log('Configuration updated:', newConfig);

275

// Update application settings

276

updateAppSettings(newConfig);

277

});

278

```

279

280

### Build System File Watching

281

282

File watching patterns commonly used in build systems and development tools.

283

284

```javascript

285

// Development server with auto-reload

286

class DevServer {

287

constructor(srcPath, buildPath) {

288

this.srcPath = srcPath;

289

this.buildPath = buildPath;

290

this.building = false;

291

this.pendingBuild = false;

292

293

this.watcher = fs.watch(srcPath, { recursive: true }, (eventType, filename) => {

294

if (filename && this.shouldTriggerBuild(filename)) {

295

this.scheduleBuild();

296

}

297

});

298

}

299

300

shouldTriggerBuild(filename) {

301

// Only build for source files

302

const sourceExtensions = ['js', 'ts', 'jsx', 'tsx', 'css', 'scss'];

303

const ext = filename.split('.').pop()?.toLowerCase();

304

return sourceExtensions.includes(ext);

305

}

306

307

scheduleBuild() {

308

if (this.building) {

309

this.pendingBuild = true;

310

return;

311

}

312

313

// Debounce builds

314

setTimeout(() => {

315

this.runBuild();

316

}, 200);

317

}

318

319

async runBuild() {

320

if (this.building) return;

321

322

this.building = true;

323

console.log('Building...');

324

325

try {

326

await this.performBuild();

327

console.log('Build completed');

328

329

if (this.pendingBuild) {

330

this.pendingBuild = false;

331

setTimeout(() => this.runBuild(), 100);

332

}

333

} catch (err) {

334

console.error('Build failed:', err);

335

} finally {

336

this.building = false;

337

}

338

}

339

340

async performBuild() {

341

// Simulate build process

342

const sourceFiles = this.getAllSourceFiles(this.srcPath);

343

344

for (const file of sourceFiles) {

345

const content = fs.readFileSync(file, 'utf8');

346

const processed = this.processFile(content);

347

348

const outputPath = file.replace(this.srcPath, this.buildPath);

349

fs.mkdirSync(path.dirname(outputPath), { recursive: true });

350

fs.writeFileSync(outputPath, processed);

351

}

352

}

353

354

getAllSourceFiles(dir) {

355

const files = [];

356

const entries = fs.readdirSync(dir, { withFileTypes: true });

357

358

for (const entry of entries) {

359

const fullPath = `${dir}/${entry.name}`;

360

if (entry.isDirectory()) {

361

files.push(...this.getAllSourceFiles(fullPath));

362

} else if (this.shouldTriggerBuild(entry.name)) {

363

files.push(fullPath);

364

}

365

}

366

367

return files;

368

}

369

370

processFile(content) {

371

// Simulate file processing

372

return content.replace(/\/\*\s*DEBUG\s*\*\/.*?\/\*\s*END_DEBUG\s*\*\//gs, '');

373

}

374

375

stop() {

376

if (this.watcher) {

377

this.watcher.close();

378

}

379

}

380

}

381

382

// Usage

383

const devServer = new DevServer('/src', '/build');

384

```

385

386

### Testing File Watching

387

388

File watching utilities for testing scenarios.

389

390

```javascript

391

// Test helper for file watching

392

class FileWatchTester {

393

constructor() {

394

this.events = [];

395

this.watchers = [];

396

}

397

398

watchFile(path) {

399

const watcher = fs.watch(path, (eventType, filename) => {

400

this.events.push({

401

eventType,

402

filename,

403

timestamp: Date.now(),

404

path

405

});

406

});

407

408

this.watchers.push(watcher);

409

return watcher;

410

}

411

412

clearEvents() {

413

this.events = [];

414

}

415

416

getEvents() {

417

return [...this.events];

418

}

419

420

waitForEvent(timeout = 1000) {

421

return new Promise((resolve, reject) => {

422

const startLength = this.events.length;

423

const timer = setTimeout(() => {

424

reject(new Error('Timeout waiting for file event'));

425

}, timeout);

426

427

const checkForEvent = () => {

428

if (this.events.length > startLength) {

429

clearTimeout(timer);

430

resolve(this.events[this.events.length - 1]);

431

} else {

432

setTimeout(checkForEvent, 10);

433

}

434

};

435

436

checkForEvent();

437

});

438

}

439

440

cleanup() {

441

this.watchers.forEach(watcher => watcher.close());

442

this.watchers = [];

443

this.events = [];

444

}

445

}

446

447

// Usage in tests

448

async function testFileWatching() {

449

const tester = new FileWatchTester();

450

451

try {

452

// Create test file

453

fs.writeFileSync('/test.txt', 'initial content');

454

455

// Start watching

456

tester.watchFile('/test.txt');

457

458

// Modify file

459

fs.writeFileSync('/test.txt', 'modified content');

460

461

// Wait for event

462

const event = await tester.waitForEvent();

463

console.log('Received event:', event);

464

465

// Check event type

466

assert.equal(event.eventType, 'change');

467

assert.equal(event.filename, '/test.txt');

468

} finally {

469

tester.cleanup();

470

}

471

}

472

```

473

474

## Encoding Options

475

476

Handle different filename encodings in watch events.

477

478

```javascript

479

// Watch with buffer encoding

480

const bufferWatcher = fs.watch('/files', { encoding: 'buffer' }, (eventType, filename) => {

481

if (filename instanceof Buffer) {

482

console.log('Filename as buffer:', filename);

483

console.log('Filename as string:', filename.toString('utf8'));

484

}

485

});

486

487

// Watch with hex encoding

488

const hexWatcher = fs.watch('/files', { encoding: 'hex' }, (eventType, filename) => {

489

console.log('Filename in hex:', filename);

490

const originalName = Buffer.from(filename, 'hex').toString('utf8');

491

console.log('Original filename:', originalName);

492

});

493

494

// Handle encoding errors gracefully

495

function createSafeWatcher(path, encoding = 'utf8') {

496

return fs.watch(path, { encoding }, (eventType, filename) => {

497

try {

498

if (filename) {

499

console.log(`${eventType}: ${filename}`);

500

} else {

501

console.log(`${eventType}: (filename not provided)`);

502

}

503

} catch (err) {

504

console.error('Error processing watch event:', err);

505

}

506

});

507

}

508

```

509

510

## Error Handling and Cleanup

511

512

Proper error handling and resource cleanup for file watchers.

513

514

```javascript

515

// Robust watcher with error handling

516

function createRobustWatcher(path, options = {}) {

517

let watcher = null;

518

let reconnectTimer = null;

519

const maxRetries = 5;

520

let retryCount = 0;

521

522

function startWatcher() {

523

try {

524

watcher = fs.watch(path, options, (eventType, filename) => {

525

retryCount = 0; // Reset retry count on successful event

526

console.log(`${eventType}: ${filename}`);

527

});

528

529

watcher.on('error', (err) => {

530

console.error('Watcher error:', err);

531

532

if (retryCount < maxRetries) {

533

retryCount++;

534

console.log(`Attempting to reconnect (${retryCount}/${maxRetries})...`);

535

536

reconnectTimer = setTimeout(() => {

537

startWatcher();

538

}, 1000 * retryCount); // Exponential backoff

539

} else {

540

console.error('Max retries reached, giving up');

541

}

542

});

543

544

watcher.on('close', () => {

545

console.log('Watcher closed');

546

});

547

548

console.log('Watcher started successfully');

549

} catch (err) {

550

console.error('Failed to start watcher:', err);

551

}

552

}

553

554

startWatcher();

555

556

// Return cleanup function

557

return () => {

558

if (reconnectTimer) {

559

clearTimeout(reconnectTimer);

560

}

561

if (watcher) {

562

watcher.close();

563

}

564

};

565

}

566

567

// Usage

568

const cleanup = createRobustWatcher('/important-files', { recursive: true });

569

570

// Clean up when done

571

process.on('SIGINT', () => {

572

cleanup();

573

process.exit(0);

574

});

575

```