From fb0f1f0ec8e66d067462b94e182152f3eb596376 Mon Sep 17 00:00:00 2001 From: Jonathan Cammisuli Date: Wed, 19 Jul 2023 18:05:52 -0400 Subject: [PATCH] fix(core): correctly output created and update events for the watcher on macos (#18186) Co-authored-by: FrozenPandaz --- packages/nx/src/native/tests/watcher.spec.ts | 36 ++++++++++-------- packages/nx/src/native/watch/types.rs | 40 +++++++++++++++++--- packages/nx/src/native/watch/watch_config.rs | 1 - packages/nx/src/native/watch/watcher.rs | 1 - packages/nx/src/utils/testing/temp-fs.ts | 14 +++---- 5 files changed, 61 insertions(+), 31 deletions(-) diff --git a/packages/nx/src/native/tests/watcher.spec.ts b/packages/nx/src/native/tests/watcher.spec.ts index 9b94b815f3f88..f5d822bf1461c 100644 --- a/packages/nx/src/native/tests/watcher.spec.ts +++ b/packages/nx/src/native/tests/watcher.spec.ts @@ -27,7 +27,7 @@ describe('watcher', () => { }); it('should trigger the callback for files that are not ignored', (done) => { - watcher = new Watcher(realpathSync(temp.tempDir)); + watcher = new Watcher(temp.tempDir); watcher.watch((error, paths) => { expect(paths).toMatchInlineSnapshot(` [ @@ -48,7 +48,7 @@ describe('watcher', () => { }); it('should trigger the callback when files are updated', (done) => { - watcher = new Watcher(realpathSync(temp.tempDir)); + watcher = new Watcher(temp.tempDir); watcher.watch((err, paths) => { expect(paths).toMatchInlineSnapshot(` @@ -62,7 +62,7 @@ describe('watcher', () => { done(); }); - wait().then(() => { + wait(1000).then(() => { // nxignored file should not trigger a callback temp.appendFile('app2/main.js', 'update'); temp.appendFile('app1/main.js', 'update'); @@ -70,18 +70,22 @@ describe('watcher', () => { }); it('should watch file renames', (done) => { - watcher = new Watcher(realpathSync(temp.tempDir)); + watcher = new Watcher(temp.tempDir); watcher.watch((err, paths) => { expect(paths.length).toBe(2); - expect(paths.find((p) => p.type === 'update')).toMatchObject({ - path: 'app1/rename.js', - type: 'update', - }); - expect(paths.find((p) => p.type === 'delete')).toMatchObject({ - path: 'app1/main.js', - type: 'delete', - }); + expect(paths.find((p) => p.type === 'create')).toMatchInlineSnapshot(` + { + "path": "app1/rename.js", + "type": "create", + } + `); + expect(paths.find((p) => p.type === 'delete')).toMatchInlineSnapshot(` + { + "path": "app1/main.js", + "type": "delete", + } + `); done(); }); @@ -91,7 +95,7 @@ describe('watcher', () => { }); it('should trigger on deletes', (done) => { - watcher = new Watcher(realpathSync(temp.tempDir)); + watcher = new Watcher(temp.tempDir); watcher.watch((err, paths) => { expect(paths).toMatchInlineSnapshot(` @@ -111,7 +115,7 @@ describe('watcher', () => { }); it('should ignore nested gitignores', (done) => { - watcher = new Watcher(realpathSync(temp.tempDir)); + watcher = new Watcher(temp.tempDir); watcher.watch((err, paths) => { expect(paths).toMatchInlineSnapshot(` @@ -133,10 +137,10 @@ describe('watcher', () => { }); }); -function wait() { +function wait(timeout = 500) { return new Promise((res) => { setTimeout(() => { res(); - }, 500); + }, timeout); }); } diff --git a/packages/nx/src/native/watch/types.rs b/packages/nx/src/native/watch/types.rs index 433378d2b7079..21fe0f94dacd8 100644 --- a/packages/nx/src/native/watch/types.rs +++ b/packages/nx/src/native/watch/types.rs @@ -1,7 +1,9 @@ use napi::bindgen_prelude::*; + use std::path::PathBuf; use tracing::trace; -use watchexec_events::filekind::FileEventKind; +use watchexec_events::filekind::ModifyKind::Name; +use watchexec_events::filekind::RenameMode; use watchexec_events::{Event, Tag}; #[napi(string_enum)] @@ -67,11 +69,37 @@ impl From<&Event> for WatchEventInternal { let event_type = if matches!(path.1, None) && !path_ref.exists() { EventType::delete } else { - match event_kind { - FileEventKind::Create(_) => EventType::create, - FileEventKind::Modify(_) => EventType::update, - FileEventKind::Remove(_) => EventType::delete, - _ => EventType::update, + #[cfg(target_os = "macos")] + { + use std::fs; + use std::os::macos::fs::MetadataExt; + + let t = fs::metadata(path_ref).expect("metadata should be available"); + + let modified_time = t.st_mtime(); + let birth_time = t.st_birthtime(); + + // if a file is created and updated near the same time, we always get a create event + // so we need to check the timestamps to see if it was created or updated + // if the modified time is the same as birth_time then it was created + if modified_time == birth_time { + EventType::create + } else { + EventType::update + } + } + + #[cfg(not(target_os = "macos"))] + { + use watchexec_events::filekind::FileEventKind; + + match event_kind { + FileEventKind::Create(_) => EventType::create, + FileEventKind::Modify(Name(RenameMode::To)) => EventType::create, + FileEventKind::Modify(Name(RenameMode::From)) => EventType::delete, + FileEventKind::Modify(_) => EventType::update, + _ => EventType::update, + } } }; diff --git a/packages/nx/src/native/watch/watch_config.rs b/packages/nx/src/native/watch/watch_config.rs index 073b6bf139319..353c20d95f808 100644 --- a/packages/nx/src/native/watch/watch_config.rs +++ b/packages/nx/src/native/watch/watch_config.rs @@ -2,7 +2,6 @@ use crate::native::watch::utils::get_ignore_files; use crate::native::watch::watch_filterer::WatchFilterer; use ignore_files::IgnoreFilter; use std::sync::Arc; -use std::time::Duration; use tracing::trace; use watchexec::config::RuntimeConfig; use watchexec_filterer_ignore::IgnoreFilterer; diff --git a/packages/nx/src/native/watch/watcher.rs b/packages/nx/src/native/watch/watcher.rs index 86616842cb60c..624deafadbf02 100644 --- a/packages/nx/src/native/watch/watcher.rs +++ b/packages/nx/src/native/watch/watcher.rs @@ -5,7 +5,6 @@ use std::path::MAIN_SEPARATOR; use std::sync::Arc; use crate::native::watch::types::{EventType, WatchEvent, WatchEventInternal}; -use itertools::Itertools; use napi::bindgen_prelude::*; use napi::threadsafe_function::{ ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode, diff --git a/packages/nx/src/utils/testing/temp-fs.ts b/packages/nx/src/utils/testing/temp-fs.ts index 0ded2e08fa2c7..47048dfc28ef8 100644 --- a/packages/nx/src/utils/testing/temp-fs.ts +++ b/packages/nx/src/utils/testing/temp-fs.ts @@ -1,17 +1,17 @@ -import { basename, dirname, join } from 'path'; +import { dirname, join } from 'path'; import { tmpdir } from 'os'; import { + emptyDirSync, + mkdirpSync, mkdtempSync, - readFile, outputFile, + readFile, + realpathSync, rmSync, - emptyDirSync, - outputFileSync, unlinkSync, - mkdirpSync, } from 'fs-extra'; import { joinPathFragments } from '../path'; -import { appendFileSync, writeFileSync, renameSync, existsSync } from 'fs'; +import { appendFileSync, existsSync, renameSync, writeFileSync } from 'fs'; type NestedFiles = { [fileName: string]: string; @@ -20,7 +20,7 @@ type NestedFiles = { export class TempFs { readonly tempDir: string; constructor(private dirname: string, overrideWorkspaceRoot = true) { - this.tempDir = mkdtempSync(join(tmpdir(), this.dirname)); + this.tempDir = realpathSync(mkdtempSync(join(tmpdir(), this.dirname))); if (overrideWorkspaceRoot) { process.env.NX_WORKSPACE_ROOT_PATH = this.tempDir; }