Skip to content

Commit fb0f1f0

Browse files
fix(core): correctly output created and update events for the watcher on macos (nrwl#18186)
Co-authored-by: FrozenPandaz <[email protected]>
1 parent 28df205 commit fb0f1f0

File tree

5 files changed

+61
-31
lines changed

5 files changed

+61
-31
lines changed

packages/nx/src/native/tests/watcher.spec.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('watcher', () => {
2727
});
2828

2929
it('should trigger the callback for files that are not ignored', (done) => {
30-
watcher = new Watcher(realpathSync(temp.tempDir));
30+
watcher = new Watcher(temp.tempDir);
3131
watcher.watch((error, paths) => {
3232
expect(paths).toMatchInlineSnapshot(`
3333
[
@@ -48,7 +48,7 @@ describe('watcher', () => {
4848
});
4949

5050
it('should trigger the callback when files are updated', (done) => {
51-
watcher = new Watcher(realpathSync(temp.tempDir));
51+
watcher = new Watcher(temp.tempDir);
5252

5353
watcher.watch((err, paths) => {
5454
expect(paths).toMatchInlineSnapshot(`
@@ -62,26 +62,30 @@ describe('watcher', () => {
6262
done();
6363
});
6464

65-
wait().then(() => {
65+
wait(1000).then(() => {
6666
// nxignored file should not trigger a callback
6767
temp.appendFile('app2/main.js', 'update');
6868
temp.appendFile('app1/main.js', 'update');
6969
});
7070
});
7171

7272
it('should watch file renames', (done) => {
73-
watcher = new Watcher(realpathSync(temp.tempDir));
73+
watcher = new Watcher(temp.tempDir);
7474

7575
watcher.watch((err, paths) => {
7676
expect(paths.length).toBe(2);
77-
expect(paths.find((p) => p.type === 'update')).toMatchObject({
78-
path: 'app1/rename.js',
79-
type: 'update',
80-
});
81-
expect(paths.find((p) => p.type === 'delete')).toMatchObject({
82-
path: 'app1/main.js',
83-
type: 'delete',
84-
});
77+
expect(paths.find((p) => p.type === 'create')).toMatchInlineSnapshot(`
78+
{
79+
"path": "app1/rename.js",
80+
"type": "create",
81+
}
82+
`);
83+
expect(paths.find((p) => p.type === 'delete')).toMatchInlineSnapshot(`
84+
{
85+
"path": "app1/main.js",
86+
"type": "delete",
87+
}
88+
`);
8589
done();
8690
});
8791

@@ -91,7 +95,7 @@ describe('watcher', () => {
9195
});
9296

9397
it('should trigger on deletes', (done) => {
94-
watcher = new Watcher(realpathSync(temp.tempDir));
98+
watcher = new Watcher(temp.tempDir);
9599

96100
watcher.watch((err, paths) => {
97101
expect(paths).toMatchInlineSnapshot(`
@@ -111,7 +115,7 @@ describe('watcher', () => {
111115
});
112116

113117
it('should ignore nested gitignores', (done) => {
114-
watcher = new Watcher(realpathSync(temp.tempDir));
118+
watcher = new Watcher(temp.tempDir);
115119

116120
watcher.watch((err, paths) => {
117121
expect(paths).toMatchInlineSnapshot(`
@@ -133,10 +137,10 @@ describe('watcher', () => {
133137
});
134138
});
135139

136-
function wait() {
140+
function wait(timeout = 500) {
137141
return new Promise<void>((res) => {
138142
setTimeout(() => {
139143
res();
140-
}, 500);
144+
}, timeout);
141145
});
142146
}

packages/nx/src/native/watch/types.rs

+34-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use napi::bindgen_prelude::*;
2+
23
use std::path::PathBuf;
34
use tracing::trace;
4-
use watchexec_events::filekind::FileEventKind;
5+
use watchexec_events::filekind::ModifyKind::Name;
6+
use watchexec_events::filekind::RenameMode;
57
use watchexec_events::{Event, Tag};
68

79
#[napi(string_enum)]
@@ -67,11 +69,37 @@ impl From<&Event> for WatchEventInternal {
6769
let event_type = if matches!(path.1, None) && !path_ref.exists() {
6870
EventType::delete
6971
} else {
70-
match event_kind {
71-
FileEventKind::Create(_) => EventType::create,
72-
FileEventKind::Modify(_) => EventType::update,
73-
FileEventKind::Remove(_) => EventType::delete,
74-
_ => EventType::update,
72+
#[cfg(target_os = "macos")]
73+
{
74+
use std::fs;
75+
use std::os::macos::fs::MetadataExt;
76+
77+
let t = fs::metadata(path_ref).expect("metadata should be available");
78+
79+
let modified_time = t.st_mtime();
80+
let birth_time = t.st_birthtime();
81+
82+
// if a file is created and updated near the same time, we always get a create event
83+
// so we need to check the timestamps to see if it was created or updated
84+
// if the modified time is the same as birth_time then it was created
85+
if modified_time == birth_time {
86+
EventType::create
87+
} else {
88+
EventType::update
89+
}
90+
}
91+
92+
#[cfg(not(target_os = "macos"))]
93+
{
94+
use watchexec_events::filekind::FileEventKind;
95+
96+
match event_kind {
97+
FileEventKind::Create(_) => EventType::create,
98+
FileEventKind::Modify(Name(RenameMode::To)) => EventType::create,
99+
FileEventKind::Modify(Name(RenameMode::From)) => EventType::delete,
100+
FileEventKind::Modify(_) => EventType::update,
101+
_ => EventType::update,
102+
}
75103
}
76104
};
77105

packages/nx/src/native/watch/watch_config.rs

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::native::watch::utils::get_ignore_files;
22
use crate::native::watch::watch_filterer::WatchFilterer;
33
use ignore_files::IgnoreFilter;
44
use std::sync::Arc;
5-
use std::time::Duration;
65
use tracing::trace;
76
use watchexec::config::RuntimeConfig;
87
use watchexec_filterer_ignore::IgnoreFilterer;

packages/nx/src/native/watch/watcher.rs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::path::MAIN_SEPARATOR;
55
use std::sync::Arc;
66

77
use crate::native::watch::types::{EventType, WatchEvent, WatchEventInternal};
8-
use itertools::Itertools;
98
use napi::bindgen_prelude::*;
109
use napi::threadsafe_function::{
1110
ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,

packages/nx/src/utils/testing/temp-fs.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { basename, dirname, join } from 'path';
1+
import { dirname, join } from 'path';
22
import { tmpdir } from 'os';
33
import {
4+
emptyDirSync,
5+
mkdirpSync,
46
mkdtempSync,
5-
readFile,
67
outputFile,
8+
readFile,
9+
realpathSync,
710
rmSync,
8-
emptyDirSync,
9-
outputFileSync,
1011
unlinkSync,
11-
mkdirpSync,
1212
} from 'fs-extra';
1313
import { joinPathFragments } from '../path';
14-
import { appendFileSync, writeFileSync, renameSync, existsSync } from 'fs';
14+
import { appendFileSync, existsSync, renameSync, writeFileSync } from 'fs';
1515

1616
type NestedFiles = {
1717
[fileName: string]: string;
@@ -20,7 +20,7 @@ type NestedFiles = {
2020
export class TempFs {
2121
readonly tempDir: string;
2222
constructor(private dirname: string, overrideWorkspaceRoot = true) {
23-
this.tempDir = mkdtempSync(join(tmpdir(), this.dirname));
23+
this.tempDir = realpathSync(mkdtempSync(join(tmpdir(), this.dirname)));
2424
if (overrideWorkspaceRoot) {
2525
process.env.NX_WORKSPACE_ROOT_PATH = this.tempDir;
2626
}

0 commit comments

Comments
 (0)