Skip to content

Commit

Permalink
fix(core): always load nxignore last for highest priority (#20104)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cammisuli authored Nov 7, 2023
1 parent 25d6ec3 commit d4ef5c0
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 69 deletions.
124 changes: 71 additions & 53 deletions packages/nx/src/native/tests/watcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,130 +27,148 @@ describe('watcher', () => {
temp.cleanup();
});

it('should trigger the callback for files that are not ignored', (done) => {
watcher = new Watcher(temp.tempDir);
watcher.watch((error, paths) => {
expect(paths).toMatchInlineSnapshot(`
it('should trigger the callback for files that are not ignored', async () => {
return new Promise<void>(async (done) => {
await wait();

watcher = new Watcher(temp.tempDir);
watcher.watch((error, paths) => {
expect(paths).toMatchInlineSnapshot(`
[
{
"path": "app1/main.html",
"type": "create",
},
]
`);
done();
});
done();
});

wait().then(() => {
await wait();
temp.createFileSync('node_modules/my-file.json', JSON.stringify({}));
await wait();
temp.createFileSync('app2/main.css', JSON.stringify({}));
await wait();
temp.createFileSync('app1/main.html', JSON.stringify({}));
});
});
}, 10000);

it('should trigger the callback when files are updated', (done) => {
watcher = new Watcher(temp.tempDir);
it('should trigger the callback when files are updated', async () => {
return new Promise<void>(async (done) => {
await wait();
watcher = new Watcher(temp.tempDir);

watcher.watch((err, paths) => {
expect(paths).toMatchInlineSnapshot(`
watcher.watch((err, paths) => {
expect(paths).toMatchInlineSnapshot(`
[
{
"path": "app1/main.js",
"type": "update",
},
]
`);
done();
});
done();
});

wait(1000).then(() => {
await wait();
// nxignored file should not trigger a callback
temp.appendFile('app2/main.js', 'update');
await wait();
temp.appendFile('app1/main.js', 'update');
});
});
}, 10000);

it('should watch file renames', (done) => {
watcher = new Watcher(temp.tempDir);
it('should watch file renames', async () => {
return new Promise<void>(async (done) => {
await wait();
watcher = new Watcher(temp.tempDir);

watcher.watch((err, paths) => {
expect(paths.length).toBe(2);
expect(paths.find((p) => p.type === 'create')).toMatchInlineSnapshot(`
watcher.watch((err, paths) => {
expect(paths.length).toBe(2);
expect(paths.find((p) => p.type === 'create')).toMatchInlineSnapshot(`
{
"path": "app1/rename.js",
"type": "create",
}
`);
expect(paths.find((p) => p.type === 'delete')).toMatchInlineSnapshot(`
expect(paths.find((p) => p.type === 'delete')).toMatchInlineSnapshot(`
{
"path": "app1/main.js",
"type": "delete",
}
`);
done();
});
done();
});

wait().then(() => {
await wait();
temp.renameFile('app1/main.js', 'app1/rename.js');
});
});
}, 10000);

it('should trigger on deletes', (done) => {
watcher = new Watcher(temp.tempDir);
it('should trigger on deletes', async () => {
return new Promise<void>(async (done) => {
await wait();
watcher = new Watcher(temp.tempDir);

watcher.watch((err, paths) => {
expect(paths).toMatchInlineSnapshot(`
watcher.watch((err, paths) => {
expect(paths).toMatchInlineSnapshot(`
[
{
"path": "app1/main.js",
"type": "delete",
},
]
`);
done();
});
done();
});

wait().then(() => {
await wait();
temp.removeFileSync('app1/main.js');
});
});
}, 10000);

it('should ignore nested gitignores', async () => {
return new Promise<void>(async (done) => {
await wait();

it('should ignore nested gitignores', (done) => {
watcher = new Watcher(temp.tempDir);
watcher = new Watcher(temp.tempDir);

watcher.watch((err, paths) => {
expect(paths).toMatchInlineSnapshot(`
watcher.watch((err, paths) => {
expect(paths).toMatchInlineSnapshot(`
[
{
"path": "boo.txt",
"type": "create",
},
]
`);
done();
});
done();
});

wait().then(() => {
await wait();
// should not be triggered
temp.createFileSync('nested-ignore/hello1.txt', '');
await wait();
temp.createFileSync('boo.txt', '');
});
});

it('should include files that are negated in nxignore but are ignored in gitignore', (done) => {
watcher = new Watcher(temp.tempDir);
watcher.watch((err, paths) => {
expect(paths.some(({ path }) => path === '.env.local')).toBeTruthy();
done();
});

wait().then(() => {
}, 10000);

it('should include files that are negated in nxignore but are ignored in gitignore', async () => {
return new Promise<void>(async (done) => {
await wait();
watcher = new Watcher(temp.tempDir);
watcher.watch((err, paths) => {
expect(paths.some(({ path }) => path === '.env.local')).toBeTruthy();
done();
});

await wait(2000);
temp.appendFile('.env.local', 'hello');
});
});
}, 15000);
});

function wait(timeout = 500) {
function wait(timeout = 1000) {
return new Promise<void>((res) => {
setTimeout(() => {
res();
Expand Down
35 changes: 21 additions & 14 deletions packages/nx/src/native/watch/utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use ignore::WalkBuilder;
use ignore_files::IgnoreFile;
use std::path::Path;
use std::{fs, path::PathBuf};
use tracing::trace;
use watchexec_events::{Event, Tag};

pub(super) fn get_ignore_files<T: AsRef<str>>(root: T) -> Vec<IgnoreFile> {
pub(super) fn get_ignore_files<T: AsRef<str>>(root: T) -> (Vec<IgnoreFile>, Option<IgnoreFile>) {
let root = root.as_ref();

let mut walker = WalkBuilder::new(root);
Expand All @@ -13,28 +14,34 @@ pub(super) fn get_ignore_files<T: AsRef<str>>(root: T) -> Vec<IgnoreFile> {

let node_folder = PathBuf::from(root).join("node_modules");
walker.filter_entry(move |entry| !entry.path().starts_with(&node_folder));
let mut ignores = walker
let gitignore_files = walker
.build()
.flatten()
.filter(|result| {
result.path().ends_with(".nxignore") || result.path().ends_with(".gitignore")
})
.map(|result| result.path().into())
.collect::<Vec<PathBuf>>();

ignores.sort();

ignores
.into_iter()
.map(|path| {
.filter(|result| result.path().ends_with(".gitignore"))
.map(|result| {
let path: PathBuf = result.path().into();
let parent: PathBuf = path.parent().unwrap_or(&path).into();
IgnoreFile {
path,
applies_in: Some(parent),
applies_to: None,
}
})
.collect()
.collect();
(gitignore_files, get_nx_ignore(root))
}

fn get_nx_ignore<P: AsRef<Path>>(origin: P) -> Option<IgnoreFile> {
let nx_ignore_path = PathBuf::from(origin.as_ref()).join(".nxignore");
if nx_ignore_path.exists() {
Some(IgnoreFile {
path: nx_ignore_path,
applies_in: Some(origin.as_ref().into()),
applies_to: None,
})
} else {
None
}
}

// /// Get only the root level folders to watch.
Expand Down
12 changes: 10 additions & 2 deletions packages/nx/src/native/watch/watch_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pub(super) async fn create_runtime(
additional_globs: &[&str],
use_ignore: bool,
) -> napi::Result<RuntimeConfig> {
let ignore_files = if use_ignore {
let (ignore_files, nx_ignore_file) = if use_ignore {
get_ignore_files(origin)
} else {
vec![]
(vec![], None)
};

trace!(
Expand All @@ -31,6 +31,14 @@ pub(super) async fn create_runtime(
.add_globs(additional_globs, Some(&origin.into()))
.map_err(anyhow::Error::from)?;

// always add the .nxignore file after all other ignores are loaded so that it has the highest priority
if let Some(nx_ignore_file) = nx_ignore_file {
filter
.add_file(&nx_ignore_file)
.await
.map_err(anyhow::Error::from)?;
}

let mut runtime = RuntimeConfig::default();
runtime.filterer(Arc::new(WatchFilterer {
inner: IgnoreFilterer(filter),
Expand Down

0 comments on commit d4ef5c0

Please sign in to comment.