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