The hereby repo strives to document the behavior of Deno.watchFs
(which is based on Rust crate notify
) step-by-step on different systems.
Feel free to use it as reference when designing tools which rely on file watching.
I've been using The Event Guide from notify
wiki, but here a few notes from my tests:
- Path format (
/
,\\
, etc.) inevent.paths
depends of the OS. Deno.FsEvent
doesn't provideFileInfo
fields, you may then need to calllstatSync
to processevent.paths
, the downside islstatSync
is not fast enough between quick ops (like creating a file and immediatly deleting it).- MacOS filesystem operations seem to be very slow in CI. the first
mkdirSync
command may not be completed before starting watching the newly created folder withDeno.watchFs
, i.e. this is why it may first emit acreate
event. - Linux is the only OS granted with
access
events. - Linux is the only OS granted with
modify
events including two paths, which can be useful for tracking moves and renames, the downside is these events always happen last.
Clone the hereby repo and launch tests with Deno:
deno test -A --unstable
All the hereby documented behaviors here were tested with Deno APIs, these may not reflect the ones you'll get when interacting with the watched source using any third-party tool.
From my own experience, when using Visual Studio Code, additional modify
events may be emitted on any scenario, e.g. 3~4 modify
events may happen for a single file save.
This is the equivalent of creating an empty file entry (touch A.txt
).
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), '')
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
}
]
This is the equivalent of creating a file entry and saving new content (echo "Hello world" > A.txt
).
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Hello world')
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
}
]
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Foo')
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Bar')
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/jejw8egqf/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/jejw8egqf/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/jejw8egqf/A.txt" ]
},
/* All platforms */
{
kind: "modify",
paths: [ "<PATH>/__TEST__/jejw8egqf/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/jejw8egqf/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/jejw8egqf/A.txt" ]
}
]
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.mkdirSync(join('<PATH>', 'foo'), { recursive: true })
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/7ml0dad18/foo" ]
}
]
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Hello world')
Deno.copyFileSync(join('<PATH>', 'A.txt'), join('<PATH>', 'B.txt'))
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/e9izorrnz/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/e9izorrnz/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/e9izorrnz/A.txt" ]
}
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/e9izorrnz/B.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/e9izorrnz/B.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/e9izorrnz/B.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/e9izorrnz/B.txt" ]
}
]
Folder items will also emit events like if they were created into the new folder, with one additional modify
event.
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
import { copySync } from 'https://deno.land/std@0.95.0/fs/mod.ts'
Deno.mkdirSync(join('<PATH>', 'foo'), { recursive: true })
Deno.writeTextFileSync(join('<PATH>', 'foo/A.txt'), 'Hello world')
copySync(join('<PATH>', 'foo'), join('<PATH>', 'bar'))
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/nk89mg2j0/foo" ]
},
{
kind: "create",
paths: [ "<PATH>/__TEST__/nk89mg2j0/foo/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/nk89mg2j0/foo/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/nk89mg2j0/A.txt" ]
},
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/nk89mg2j0/bar" ]
},
{
kind: "create",
paths: [ "<PATH>/__TEST__/nk89mg2j0/bar/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/nk89mg2j0/bar/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/nk89mg2j0/bar/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/nk89mg2j0/bar/A.txt" ]
}
]
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
import { moveSync } from 'https://deno.land/std@0.95.0/fs/mod.ts'
Deno.mkdirSync(join('<PATH>', 'foo'), { recursive: true })
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Hello world')
moveSync(join('<PATH>', 'A.txt'), join('<PATH>', 'foo/A.txt'), { overwrite: true })
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/j3dset8ut/foo" ]
},
{
kind: "create",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/j3dset8ut/A.txt" ]
},
/* All platforms */
{
kind: "modify",
paths: [ "<PATH>/__TEST__/i3h6cswpt/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/i3h6cswpt/foo/A.txt" ]
},
/* Only on Linux */
{
kind: "modify",
paths: [
"<PATH>/__TEST__/i3h6cswpt/A.txt",
"<PATH>/__TEST__/i3h6cswpt/foo/A.txt"
]
}
]
Unlike folder copies, folder items won't emit events, so you may have to walk the new folder and register items.
Deno.mkdirSync(join('<PATH>', 'foo'), { recursive: true })
Deno.mkdirSync(join('<PATH>', 'bar'), { recursive: true })
moveSync(join('<PATH>', 'foo'), join('<PATH>', 'bar/foo/'), { overwrite: true })
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/k5nl2tstq/foo" ]
},
{
kind: "create",
paths: [ "<PATH>/__TEST__/k5nl2tstq/bar" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/k5nl2tstq/foo" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/k5nl2tstq/bar/foo" ]
},
/* Only on Linux */
{
kind: "modify",
paths: [
"<PATH>/__TEST__/k5nl2tstq/foo",
"<PATH>/__TEST__/k5nl2tstq/bar/foo"
]
}
]
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Hello world')
Deno.renameSync(join('<PATH>', 'A.txt'), join('<PATH>', 'B.txt'))
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/omi3xk5p4/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/omi3xk5p4/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/omi3xk5p4/A.txt" ]
},
/* All platforms */
{
kind: "modify",
paths: [ "<PATH>/__TEST__/omi3xk5p4/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/omi3xk5p4/B.txt" ]
},
/* Only on Linux */
{
kind: "modify",
paths: [
"<PATH>/__TEST__/omi3xk5p4/A.txt",
"<PATH>/__TEST__/omi3xk5p4/B.txt"
]
}
]
Note that only the folder will emit events, while folder items won't.
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.mkdirSync(join('<PATH>', 'foo'), { recursive: true })
Deno.renameSync(join('<PATH>', 'foo'), join('<PATH>', 'bar'))
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/1k8v51qih/foo" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/1k8v51qih/foo" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/1k8v51qih/bar" ]
},
/* Only on Linux */
{
kind: "modify",
paths: [
"<PATH>/__TEST__/1k8v51qih/foo",
"<PATH>/__TEST__/1k8v51qih/bar"
]
}
]
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.writeTextFileSync(join('<PATH>', 'A.txt'), 'Hello world')
Deno.removeSync(join('<PATH>', 'A.txt'))
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/0c34facb9/A.txt" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/0c34facb9/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/0c34facb9/A.txt" ]
},
/* All platforms */
{
kind: "remove",
paths: [ "<PATH>/__TEST__/0c34facb9/A.txt" ]
}
]
Folder items gets removed first and emit remove
events, then the folder gets removed and emits a remove
event.
import { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
Deno.mkdirSync(join('<PATH>', 'foo'), { recursive: true })
Deno.writeTextFileSync(join('<PATH>', 'foo/A.txt'), 'Hello world')
Deno.removeSync(join('<PATH>', 'foo'), { recursive: true })
Result:
[
/* All platforms */
{
kind: "create",
paths: [ "<PATH>/__TEST__/wqaw5fl3l/foo" ]
},
{
kind: "modify",
paths: [ "<PATH>/__TEST__/wqaw5fl3l/foo/A.txt" ]
},
/* Only on Linux */
{
kind: "access",
paths: [ "<PATH>/__TEST__/wqaw5fl3l/foo/A.txt" ]
},
/* All platforms */
{
kind: "remove",
paths: [ "<PATH>/__TEST__/wqaw5fl3l/foo/A.txt" ]
},
{
kind: "remove",
paths: [ "<PATH>/__TEST__/wqaw5fl3l/foo" ]
}
]