Simple cross platform file watcher for running commands when files change.
Built with C#, .NET 11 and FileSystemWatcher API.
- Cross platform
- Multiple watchers
- Recursive directory watching
- Regex based file matching
- Debounce support
- Live stdout and stderr streaming
- JSON config (comments and trailing commas supported)
- Built-in token replacement in commands, arguments, and environment variables
- Escape sequences to prevent token substitution
- JSON-based substitutions with optional caching
- Custom environment variables per watcher
- Skip empty files
dotnet restore && dotnet build -c Releasedotnet run -- watcher.config.jsonIf the config file does not exist in current directory, WatchExec creates an example config automatically.
{
"watchers": [
{
"name": "TypeScript Builder",
"path": "./src",
"includeSubdirectories": true,
"regexPattern": ".*\\.(ts|tsx|js|jsx)$",
"debounceMilliseconds": 500,
"notifyFilters": ["FileName", "LastWrite"],
"command": "bash",
"arguments": ["-c", "echo Changed: {fullpath}"],
"waitForExit": true,
"workingDirectory": ".",
"environmentVariables": {
"NODE_ENV": "development",
"BUILD_MODE": "watch"
}
}
]
}| Field | Description |
|---|---|
name |
Watcher name |
path |
Directory to watch |
includeSubdirectories |
Watch subdirectories |
regexPattern |
Regex applied to normalized full paths |
debounceMilliseconds |
Delay before triggering command |
notifyFilters |
FileSystemWatcher notify filters |
command |
Executable or shell command |
arguments |
Command arguments |
workingDirectory |
Working directory for the process |
waitForExit |
Wait for command completion |
internalBufferSizeKb |
File watcher buffer size (4–64 KB) |
skipEmptyFiles |
Skip events for empty or missing files |
substitutionJsonPath |
Path to a JSON file for custom substitutions |
cacheSubstitutionJson |
Cache the substitution JSON at startup instead of re-reading |
environmentVariables |
Key-value pairs set as environment variables for the process |
These tokens can be used inside commands, arguments, and environment variable values.
| Token | Description |
|---|---|
{fullpath} |
Full file path |
{directory} |
File directory |
{filename} |
File name with extension |
{name} |
File name without extension |
{extension} |
File extension (including dot) |
{event} |
Event type (Created, Changed, Deleted, Renamed) |
{timestamp} |
Current Unix timestamp in milliseconds |
{oldfullpath} |
Previous file path (rename events only) |
{oldfilename} |
Previous file name (rename events only) |
Any custom keys from a substitution JSON file are also available as tokens.
Use double braces {{ and }} to prevent token substitution. The double braces are converted to literal { and } in the output.
Example:
"echo {fullpath}"→echo /path/to/file.txt"echo {{fullpath}}"→echo {fullpath}
You can load custom key-value pairs from a JSON file and use them as tokens in commands, arguments, and environment variables.
{
"name": "Deploy",
"path": "./src",
"regexPattern": ".*\\.json$",
"command": "bash",
"arguments": ["-c", "echo Deploying to {environment} with key {apikey}"],
"substitutionJsonPath": "substitutions.json",
"cacheSubstitutionJson": true
}{
"environment": "production",
"apikey": "abc123",
"server": "api.example.com",
"filepath": "{fullpath}"
}- Token substitution is supported inside JSON values (e.g.
"{fullpath}"in the JSON file will be replaced with the actual file path). - Set
"cacheSubstitutionJson": trueto load the JSON once at startup. Whenfalse(default), the file is re-read on every event.
Set custom environment variables for the spawned process. Token substitution is supported in values.
{
"name": "Node Server",
"path": "./src",
"regexPattern": ".*\\.js$",
"command": "node",
"arguments": ["server.js"],
"environmentVariables": {
"NODE_ENV": "production",
"API_KEY": "secret123",
"WATCHED_FILE": "{fullpath}"
}
}{
"name": "DotNet Tests",
"path": "./tests",
"regexPattern": ".*\\.cs$",
"command": "dotnet",
"arguments": ["test", "--nologo"],
"waitForExit": true
}{
"name": "Lux Build",
"path": "./src",
"regexPattern": ".*\\.lux$",
"command": "lux",
"arguments": ["build", "{fullpath}"]
}{
"name": "Deploy",
"path": "./config",
"regexPattern": ".*\\.json$",
"command": "bash",
"arguments": ["-c", "deploy --env {environment} --server {server}"],
"substitutionJsonPath": "deploy.json",
"cacheSubstitutionJson": true,
"environmentVariables": {
"DEPLOY_ENV": "{environment}"
}
}dotnet publish -c Release -r linux-x64 --self-contained truedotnet publish -c Release -r win-x64 --self-contained truedotnet publish -c Release -r osx-arm64 --self-contained true- Uses normalized absolute paths (forward slashes) for regex matching
- Debouncing reduces duplicate file events
- Child process output is streamed live
- JSON comments and trailing commas are supported
- Multiple watchers can run at the same time
- Escape tokens with double braces
{{token}}to output literal braces