Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/watch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,23 @@ export const watchCommand = command({
process.on('SIGINT', relaySignal);
process.on('SIGTERM', relaySignal);

const cwd = process.cwd();

/**
* Resolve relative exclude patterns against cwd so that patterns like
* `../sibling/**` are matched by chokidar regardless of platform.
* Patterns that start with `../` or `./` are made absolute; pure glob
* patterns such as `**\/*.ts` are left unchanged so they continue to
* match relative paths inside cwd.
*/
const resolvedExclude = options.exclude.map(
pattern => (
pattern.startsWith('../') || pattern.startsWith('./')
? path.resolve(cwd, pattern)
: pattern
),
);

/**
* Ideally, we can get a list of files loaded from the run above
* and only watch those files, but it's not possible to detect
Expand All @@ -213,7 +230,7 @@ export const watchCommand = command({
...options.include,
],
{
cwd: process.cwd(),
cwd,
ignoreInitial: true,
ignored: [
// Hidden directories like .git
Expand All @@ -225,7 +242,7 @@ export const watchCommand = command({
// 3rd party packages
'**/{node_modules,bower_components,vendor}/**',

...options.exclude,
...resolvedExclude,
],
ignorePermissionErrors: true,
},
Expand Down
54 changes: 54 additions & 0 deletions tests/specs/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,60 @@ export const watch = ({ tsx }: NodeApis) => describe('watch', async () => {
});

await describe('exclude (ignore)', () => {
test('parent/adjacent directory', async () => {
// Regression test for https://github.com/privatenumber/tsx/issues/785
// --exclude patterns starting with "../" should suppress restarts
// triggered by files in parent or adjacent directories.

await using fixtureParent = await createFixture({
// The app under watch runs from "app/" with cwd = <fixture>/app
'app/index.ts': `
import { value } from '../sibling/dep.ts';
console.log('running:', value);
`.trim(),
'sibling/dep.ts': 'export const value = "initial"',
});

const appDirectory = fixtureParent.getPath('app');

const tsxProcess = tsx(
[
'watch',
'--clear-screen=false',
'--exclude=../sibling/**',
'index.ts',
],
appDirectory,
);
const negativeSignal = 'fail';

await expect(
processInteract(
tsxProcess.stdout!,
[
async ({ output }) => {
if (!output.includes('running: initial\n')) {
return;
}

// Change in the excluded sibling directory must not trigger a re-run
await fixtureParent.writeFile('sibling/dep.ts', `export const value = "${negativeSignal}"`);
return true;
},
({ output }) => {
if (output.includes(negativeSignal)) {
throw new Error('Unexpected re-run triggered by excluded sibling file');
}
},
],
2000,
),
).rejects.toThrow('Timeout'); // Watcher must remain quiet

tsxProcess.kill();
await tsxProcess;
}, 15_000);

test('file path & glob', async () => {
const entryFile = 'index.js';
const fileA = 'file-a.js';
Expand Down