Skip to main content

Watch Mode

File watching and automatic rebuilding for development workflow.

Overview

Watch Mode provides:

  • File change detection
  • Debounced callbacks
  • Multiple path watching
  • Glob pattern support
  • Add/delete event handling

Installation

import {
createWatcher,
watchFiles,
watchWithRebuild,
} from 'cardano-devkit';

Quick Start

import { createWatcher } from 'cardano-devkit';

const watcher = createWatcher({
paths: ['./src/**/*.ts'],
onChange: (files) => {
console.log('Files changed:', files);
// Trigger rebuild, tests, etc.
},
});

await watcher.start();

Creating a Watcher

Basic Configuration

const watcher = createWatcher({
paths: ['./src'],
onChange: async (files) => {
console.log('Changed:', files);
},
});

await watcher.start();

Full Configuration

const watcher = createWatcher({
// Paths to watch (files or directories)
paths: ['./src', './contracts'],

// Called when files change
onChange: async (files) => {
console.log('Files changed:', files);
await runBuild();
},

// Called when files are added
onAdd: async (file) => {
console.log('File added:', file);
},

// Called when files are deleted
onDelete: async (file) => {
console.log('File deleted:', file);
},

// Debounce delay in milliseconds
debounce: 100,

// Patterns to ignore
ignore: ['node_modules', '.git', 'dist'],

// Run onChange immediately on start
runOnStart: false,

// Enable verbose logging
verbose: true,
});

Watcher API

Starting and Stopping

// Start watching
await watcher.start();

// Check if watching
console.log('Active:', watcher.isWatching());

// Stop watching
await watcher.stop();

Managing Paths

// Get watched paths
const paths = watcher.getWatchedPaths();
console.log('Watching:', paths);

// Add a new path
watcher.addPath('./lib');

// Remove a path
watcher.removePath('./old-src');

Events

The watcher emits events:

watcher.on('start', () => {
console.log('Watcher started');
});

watcher.on('stop', () => {
console.log('Watcher stopped');
});

watcher.on('change', (event) => {
console.log('Change event:', event);
});

watcher.on('error', (error) => {
console.error('Watcher error:', error);
});

Watch with Rebuild

Convenience function for build workflows:

import { watchWithRebuild } from 'cardano-devkit';

await watchWithRebuild({
paths: ['./src/**/*.ts'],
buildCommand: 'pnpm build',
testCommand: 'pnpm test',
runTestsOnChange: true,
});

CLI Usage

# Watch src directory and run build on changes
cardano-devkit watch --path ./src --command "pnpm build"

# Watch multiple paths
cardano-devkit watch --path ./src --path ./lib --command "pnpm build"

# With tests
cardano-devkit watch --path ./src --command "pnpm build" --test "pnpm test"

Use Cases

Aiken Contract Development

const watcher = createWatcher({
paths: ['./validators/**/*.ak'],
onChange: async () => {
console.log('Rebuilding Aiken contracts...');
await exec('aiken build');
console.log('Done!');
},
});

TypeScript + Aiken

const watcher = createWatcher({
paths: ['./src/**/*.ts', './validators/**/*.ak'],
onChange: async (files) => {
// Check what changed
const aikenChanged = files.some(f => f.endsWith('.ak'));
const tsChanged = files.some(f => f.endsWith('.ts'));

if (aikenChanged) {
await exec('aiken build');
}

if (tsChanged) {
await exec('tsc');
}
},
});

Test Runner

const watcher = createWatcher({
paths: ['./src/**/*.ts', './src/**/*.test.ts'],
onChange: async (files) => {
const testFiles = files.filter(f => f.includes('.test.'));

if (testFiles.length > 0) {
// Run specific tests
await exec(`pnpm jest ${testFiles.join(' ')}`);
} else {
// Run related tests
await exec('pnpm test');
}
},
debounce: 500, // Wait for multiple saves
});

Live Reload Development

const watcher = createWatcher({
paths: ['./src/**/*'],
onChange: async () => {
await rebuild();
// Notify browser/client to reload
broadcastReload();
},
runOnStart: true, // Build on start
});

Types

WatchConfig

interface WatchConfig {
// Paths or glob patterns to watch
paths: string[];

// Callback when files change
onChange?: (files: string[]) => void | Promise<void>;

// Callback when files are added
onAdd?: (file: string) => void | Promise<void>;

// Callback when files are deleted
onDelete?: (file: string) => void | Promise<void>;

// Debounce delay in ms (default: 100)
debounce?: number;

// Patterns to ignore (default: ['node_modules', '.git', 'dist'])
ignore?: string[];

// Run onChange on start (default: false)
runOnStart?: boolean;

// Verbose logging (default: false)
verbose?: boolean;
}

WatchEvent

interface WatchEvent {
type: 'change' | 'add' | 'delete' | 'error';
path: string;
timestamp: Date;
}

Watcher

interface Watcher extends EventEmitter {
start(): Promise<void>;
stop(): Promise<void>;
isWatching(): boolean;
getWatchedPaths(): string[];
addPath(path: string): void;
removePath(path: string): void;
}

Best Practices

  1. Use debouncing - Prevent rapid rebuilds from multiple file saves
  2. Ignore build outputs - Don't watch directories you're writing to
  3. Selective rebuilds - Only rebuild what changed
  4. Graceful shutdown - Always call stop() on exit
  5. Error handling - Listen to error events
// Proper cleanup
process.on('SIGINT', async () => {
await watcher.stop();
process.exit(0);
});